mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-13 12:42:06 -05:00
Compare commits
1455
Commits
2.8.11-pre4
..
3.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0d8d479bd | ||
|
|
2d46d70c92 | ||
|
|
4320bb7cb4 | ||
|
|
398e8ed5ff | ||
|
|
4e8b804337 | ||
|
|
de7c664e0d | ||
|
|
c7600a2e3d | ||
|
|
8b34e82aab | ||
|
|
c96efe8802 | ||
|
|
0a1bfd3a2e | ||
|
|
755cf9ce43 | ||
|
|
c6aca74396 | ||
|
|
b5b7f3c16c | ||
|
|
293162b484 | ||
|
|
bd543b7b9d | ||
|
|
994fe9f482 | ||
|
|
f657bdb14c | ||
|
|
a4238e1191 | ||
|
|
daf08423b4 | ||
|
|
91367f3d3a | ||
|
|
9dbd52b638 | ||
|
|
22c6be2770 | ||
|
|
fb261c9f90 | ||
|
|
638e35bf90 | ||
|
|
25d13862ae | ||
|
|
1c8f483138 | ||
|
|
92ad215873 | ||
|
|
7ed3cfbb7c | ||
|
|
53e75a27be | ||
|
|
1b67819c3c | ||
|
|
07f8020f79 | ||
|
|
b12d0680d8 | ||
|
|
64247d913e | ||
|
|
3575cdae85 | ||
|
|
f71a542247 | ||
|
|
46cc59138b | ||
|
|
46bfa5112e | ||
|
|
fcdff51783 | ||
|
|
a4be4ae419 | ||
|
|
7083352b9a | ||
|
|
956a0f46c0 | ||
|
|
aea1864627 | ||
|
|
803b87111a | ||
|
|
f64a641b63 | ||
|
|
7f9bb354d7 | ||
|
|
4b7b6284f8 | ||
|
|
bbccd26415 | ||
|
|
bab7c737b3 | ||
|
|
3169787b41 | ||
|
|
f29bbc6657 | ||
|
|
f53cd5ae11 | ||
|
|
38d36b5ab5 | ||
|
|
8f8f596abc | ||
|
|
2afc8abdb2 | ||
|
|
38dd948306 | ||
|
|
e393cf0a10 | ||
|
|
4f451017e3 | ||
|
|
9cb31af48a | ||
|
|
2c17041b27 | ||
|
|
72c75a443e | ||
|
|
1ba6384647 | ||
|
|
e2edf9a37c | ||
|
|
cb0bfd095d | ||
|
|
ec03dd4755 | ||
|
|
de44f5ae42 | ||
|
|
5ee6796172 | ||
|
|
d034fca513 | ||
|
|
f65c3fb29a | ||
|
|
7192bc9d69 | ||
|
|
d7636a05f4 | ||
|
|
bd93301634 | ||
|
|
b0558ba0a2 | ||
|
|
251934bb4b | ||
|
|
f2c9ef5d30 | ||
|
|
15a072bbaa | ||
|
|
8f1c303862 | ||
|
|
c79d2e3e6e | ||
|
|
f45d8d860b | ||
|
|
88f31184a0 | ||
|
|
d5368baab4 | ||
|
|
10f645226c | ||
|
|
322c572cd6 | ||
|
|
fdfeb178b5 | ||
|
|
1c63cdba91 | ||
|
|
289d0db8e3 | ||
|
|
9adacca77f | ||
|
|
7dc6813274 | ||
|
|
77bf1cfbb9 | ||
|
|
8f28350579 | ||
|
|
2d06d0c1d3 | ||
|
|
7e8301afcd | ||
|
|
2600e8f856 | ||
|
|
914748e14e | ||
|
|
21a7b633e7 | ||
|
|
a77d4352e2 | ||
|
|
a7a21e0e51 | ||
|
|
a0a14cd285 | ||
|
|
44b7bedd53 | ||
|
|
409a0808c1 | ||
|
|
4214d02fad | ||
|
|
bc1abb1b58 | ||
|
|
2f2f53ba4d | ||
|
|
cbeb4c151d | ||
|
|
1d5ca70790 | ||
|
|
1f619ff4d3 | ||
|
|
f5acdebe60 | ||
|
|
bd59922204 | ||
|
|
c4d9482c79 | ||
|
|
8faef8f017 | ||
|
|
284161db77 | ||
|
|
78db2b66ef | ||
|
|
0d71643ecb | ||
|
|
9a7a105e7b | ||
|
|
f54d024d97 | ||
|
|
94338bd53f | ||
|
|
1a9ecc0313 | ||
|
|
4baeb7b945 | ||
|
|
baee479fb5 | ||
|
|
5ad4b2e9c4 | ||
|
|
26657cddfc | ||
|
|
947a922f8c | ||
|
|
a2be76184e | ||
|
|
8ddb733dc3 | ||
|
|
94e016129b | ||
|
|
5e653ded5d | ||
|
|
1bf8097c3f | ||
|
|
3d2f11ffd2 | ||
|
|
5e77a22d7a | ||
|
|
1dffcb1dc3 | ||
|
|
edc18cbafb | ||
|
|
2f23194432 | ||
|
|
20709472be | ||
|
|
7a8c6a0cec | ||
|
|
88558e40a7 | ||
|
|
79386b2b6b | ||
|
|
3aad8d3cb5 | ||
|
|
272a6f37ad | ||
|
|
e10081025d | ||
|
|
e03a6d8f11 | ||
|
|
98e7dc0321 | ||
|
|
74438c5d74 | ||
|
|
0c4e548565 | ||
|
|
cc70e01e96 | ||
|
|
7c635748af | ||
|
|
0c874ad56d | ||
|
|
2fb7f80b71 | ||
|
|
3f7c3414ce | ||
|
|
97165db44d | ||
|
|
82cdf04cf0 | ||
|
|
171d899609 | ||
|
|
6432278d5d | ||
|
|
ccaff12451 | ||
|
|
11347c080b | ||
|
|
553676c3f9 | ||
|
|
40b70e7418 | ||
|
|
0ea75019ae | ||
|
|
dbc092df77 | ||
|
|
68049f0dca | ||
|
|
9db5dfc175 | ||
|
|
039a316b3b | ||
|
|
f4b529f1ce | ||
|
|
9c6d606103 | ||
|
|
6cdc06aae7 | ||
|
|
2684654e7b | ||
|
|
25bcc33204 | ||
|
|
e5b3f6215b | ||
|
|
9306eadd90 | ||
|
|
a27fbb5b8b | ||
|
|
cd4c82b0b8 | ||
|
|
aa6179ee9e | ||
|
|
6594b8c2dc | ||
|
|
d4c2cdf003 | ||
|
|
2b529c37f5 | ||
|
|
8599cd925b | ||
|
|
89f37b4b9e | ||
|
|
f3136e85ee | ||
|
|
845743073c | ||
|
|
b2c7bacaf9 | ||
|
|
ed1673f032 | ||
|
|
bac9d443ea | ||
|
|
90e3868c08 | ||
|
|
c428572e01 | ||
|
|
884b5755ec | ||
|
|
32d7366795 | ||
|
|
21bf4a5450 | ||
|
|
8042b02cf5 | ||
|
|
ae7cdd3982 | ||
|
|
9ba945ad76 | ||
|
|
9cd04c3660 | ||
|
|
444bc19781 | ||
|
|
eb08c4ba04 | ||
|
|
5846e24a29 | ||
|
|
9c2c09e433 | ||
|
|
3c4329697b | ||
|
|
af9951d993 | ||
|
|
630f6df2f5 | ||
|
|
fcf07b8e7b | ||
|
|
4824c2f626 | ||
|
|
2b6b2e1b63 | ||
|
|
6926c1e57f | ||
|
|
6a00a7f1ca | ||
|
|
288c3d4c87 | ||
|
|
ac497bcc41 | ||
|
|
7b8d0d6845 | ||
|
|
a9a4589fae | ||
|
|
b8f972a774 | ||
|
|
305e388478 | ||
|
|
97e7848a78 | ||
|
|
d33058a924 | ||
|
|
eda3cc1f17 | ||
|
|
47043ad4ad | ||
|
|
acf20088cc | ||
|
|
374cb2766c | ||
|
|
31685b9ba5 | ||
|
|
722ff4ce52 | ||
|
|
161b375043 | ||
|
|
2c14191cde | ||
|
|
486546918f | ||
|
|
23d08e58a4 | ||
|
|
4c4c34ecaa | ||
|
|
bd3f2fd70a | ||
|
|
b80e42a635 | ||
|
|
9446a034d0 | ||
|
|
da0c6ab3fd | ||
|
|
c1adb27ee5 | ||
|
|
980d3027e3 | ||
|
|
53ba533f9b | ||
|
|
e40827da65 | ||
|
|
b9848b6a96 | ||
|
|
147593b11a | ||
|
|
21652f928b | ||
|
|
5b0352aff0 | ||
|
|
b2da2fa096 | ||
|
|
0cbb01a2a8 | ||
|
|
179840968d | ||
|
|
3b9c1649a1 | ||
|
|
5750c7dbe1 | ||
|
|
7ceca359be | ||
|
|
0f8152a6ed | ||
|
|
b3258bb020 | ||
|
|
4b2e705cbc | ||
|
|
31e41382f8 | ||
|
|
ecda7192dc | ||
|
|
9a48562b9f | ||
|
|
c181b58bc1 | ||
|
|
2624afa2ed | ||
|
|
3690af1e35 | ||
|
|
98966d3b38 | ||
|
|
41fdd16ce1 | ||
|
|
1bc94859c0 | ||
|
|
60f6f36437 | ||
|
|
82267ba16a | ||
|
|
14260deef8 | ||
|
|
8ba7342ccf | ||
|
|
f6b935d78e | ||
|
|
8cf3ae2091 | ||
|
|
e02b997521 | ||
|
|
229cef13c9 | ||
|
|
f503d6f896 | ||
|
|
d851166bb7 | ||
|
|
638a861350 | ||
|
|
20d7e4cbb0 | ||
|
|
36ccedd7c6 | ||
|
|
31854ba7a6 | ||
|
|
2639f07f5c | ||
|
|
b6e13f557f | ||
|
|
6333d90059 | ||
|
|
fea5d0ec56 | ||
|
|
c77f02d1fd | ||
|
|
c498965cae | ||
|
|
cdaa77071d | ||
|
|
6a72678c8d | ||
|
|
bb67fc622f | ||
|
|
5b50c02d93 | ||
|
|
ffec7e3266 | ||
|
|
039c1e7027 | ||
|
|
b27a21b3b8 | ||
|
|
2bcb5f617e | ||
|
|
7bf5294f9f | ||
|
|
c2fd1940f0 | ||
|
|
2d095ce6db | ||
|
|
e88bc36fca | ||
|
|
5613ea4f77 | ||
|
|
9c41599d60 | ||
|
|
5f8dd869e0 | ||
|
|
fb17df0b7e | ||
|
|
900ae83028 | ||
|
|
2c09db25b7 | ||
|
|
004e53fa29 | ||
|
|
e285b143d5 | ||
|
|
f78eb0ffed | ||
|
|
4d761e1d6e | ||
|
|
4a8c975319 | ||
|
|
fe56b7ed2d | ||
|
|
5a74654807 | ||
|
|
533ce4a529 | ||
|
|
4ea3780114 | ||
|
|
0d229ee26c | ||
|
|
29079ecad5 | ||
|
|
ea88401a58 | ||
|
|
298cef4d1f | ||
|
|
a51e9495de | ||
|
|
eead8ca1e2 | ||
|
|
a30712bc38 | ||
|
|
07b443fb21 | ||
|
|
61d927305c | ||
|
|
8f21c2f27d | ||
|
|
9fcf76e0c4 | ||
|
|
773afe2fb5 | ||
|
|
83ce5bd476 | ||
|
|
75a341c240 | ||
|
|
921a793b5c | ||
|
|
c1b92868cd | ||
|
|
94d19d728d | ||
|
|
8ef518f7e2 | ||
|
|
79fd1148a9 | ||
|
|
313ba8d765 | ||
|
|
c12c422357 | ||
|
|
b14c97d18a | ||
|
|
2fe345cb65 | ||
|
|
96ff81370c | ||
|
|
c4a1f41673 | ||
|
|
ca4c8ccb20 | ||
|
|
7471006f8a | ||
|
|
896c709824 | ||
|
|
473a37cb6a | ||
|
|
9e84d96051 | ||
|
|
019466d84d | ||
|
|
c222061529 | ||
|
|
acdd99404c | ||
|
|
799381bff3 | ||
|
|
b3654977ca | ||
|
|
b67d1928b5 | ||
|
|
052748d2b9 | ||
|
|
e265187ff8 | ||
|
|
c622a95550 | ||
|
|
94d71a245b | ||
|
|
a9403269be | ||
|
|
dfe6768eb5 | ||
|
|
d88bf65806 | ||
|
|
30929062d6 | ||
|
|
747e3d692d | ||
|
|
1d3ecbb7ed | ||
|
|
39b88b0dd5 | ||
|
|
d13d3eb055 | ||
|
|
f8f72b9074 | ||
|
|
a9d0b0a0ae | ||
|
|
37bd624537 | ||
|
|
e19f4ed771 | ||
|
|
c828fa8d52 | ||
|
|
9e080a5f5c | ||
|
|
7d7d4c05ab | ||
|
|
d4c7044708 | ||
|
|
52dd8c7c8b | ||
|
|
398ac8b6cf | ||
|
|
911792b214 | ||
|
|
8e54989502 | ||
|
|
4f16579063 | ||
|
|
e1f1698413 | ||
|
|
5eb999b98a | ||
|
|
5e7af835d8 | ||
|
|
f19f2c5a20 | ||
|
|
ff2aceb781 | ||
|
|
dc1410adbb | ||
|
|
49af38f3c5 | ||
|
|
09cf4dcd07 | ||
|
|
95c92285b9 | ||
|
|
81b7be9205 | ||
|
|
c308cde139 | ||
|
|
49889cd2cc | ||
|
|
d0ea71f0c9 | ||
|
|
a6f93dae1e | ||
|
|
f968999393 | ||
|
|
37348ed1f4 | ||
|
|
8602d39d7c | ||
|
|
fd41390e20 | ||
|
|
712064bced | ||
|
|
771a696de0 | ||
|
|
a56cbcc2f4 | ||
|
|
8e659a2c97 | ||
|
|
423bd0adb8 | ||
|
|
1987848f8b | ||
|
|
13ac8541f2 | ||
|
|
0e42d61ffa | ||
|
|
64b4cb1bb9 | ||
|
|
efd723deec | ||
|
|
4c2d2068a4 | ||
|
|
32529dd7e9 | ||
|
|
3ad54d5f21 | ||
|
|
6d81c37645 | ||
|
|
da1c51eb5b | ||
|
|
70c37fb9f2 | ||
|
|
5e565be35b | ||
|
|
eeb39ca228 | ||
|
|
2d5bc2bc52 | ||
|
|
4fb517c8cb | ||
|
|
de376cd9f4 | ||
|
|
d939984f08 | ||
|
|
89edcb7a38 | ||
|
|
ac16a0fb26 | ||
|
|
9189fb2082 | ||
|
|
a2e7670560 | ||
|
|
7c9f14b4d9 | ||
|
|
f05849aada | ||
|
|
66e91ee4e8 | ||
|
|
015d8de101 | ||
|
|
69e575e0ef | ||
|
|
6c84e49a7f | ||
|
|
f4e5e8fd72 | ||
|
|
fe8c738196 | ||
|
|
665dfac5ea | ||
|
|
57f9806819 | ||
|
|
7874135014 | ||
|
|
f86e7b17ef | ||
|
|
3803173745 | ||
|
|
6af30bf6df | ||
|
|
7bea6a214a | ||
|
|
9becc35fa1 | ||
|
|
7212a4cd7f | ||
|
|
c604640ab9 | ||
|
|
23fee2b7b7 | ||
|
|
c3d521960e | ||
|
|
484396cce1 | ||
|
|
ce4dec6136 | ||
|
|
67f42f518e | ||
|
|
0a0e947282 | ||
|
|
8a4127574a | ||
|
|
b29d3e5d6a | ||
|
|
daaece8d36 | ||
|
|
0e5b1aaf0e | ||
|
|
e672e49435 | ||
|
|
b9a38a486d | ||
|
|
d34c46658d | ||
|
|
e120b647ea | ||
|
|
936b8ae903 | ||
|
|
147fe2c277 | ||
|
|
84413aa490 | ||
|
|
3fea95f8c5 | ||
|
|
b839bce010 | ||
|
|
fc70c098a9 | ||
|
|
7b74d73a9a | ||
|
|
44ee3bbdfe | ||
|
|
673285baf8 | ||
|
|
2b22e327e3 | ||
|
|
95d65236f6 | ||
|
|
8971172444 | ||
|
|
6ff5836ca9 | ||
|
|
8e9a4e9601 | ||
|
|
1ce5c05622 | ||
|
|
7f83e83aed | ||
|
|
e677e7345b | ||
|
|
ae52aa2c55 | ||
|
|
b83f6e93bf | ||
|
|
3606ee6275 | ||
|
|
70571cccfe | ||
|
|
87fe02b0b4 | ||
|
|
8e48087f65 | ||
|
|
4e7c3ee2f1 | ||
|
|
60a7549662 | ||
|
|
fe7c9488d8 | ||
|
|
b7d39e55b6 | ||
|
|
805d8030b4 | ||
|
|
4c6fc5d2d2 | ||
|
|
913b8ee238 | ||
|
|
3688e4d938 | ||
|
|
03842db57f | ||
|
|
6f34157a84 | ||
|
|
86a82faa15 | ||
|
|
2e506b72a6 | ||
|
|
d2ba0a22d5 | ||
|
|
cfc3488a8c | ||
|
|
0463e82470 | ||
|
|
93b151b377 | ||
|
|
1c6fa28d87 | ||
|
|
71d01263b5 | ||
|
|
23268907bd | ||
|
|
076e3061b3 | ||
|
|
d9f8fa2ed0 | ||
|
|
c33f9a6d61 | ||
|
|
07f71f3d4d | ||
|
|
48f8a4c718 | ||
|
|
ff29b1badf | ||
|
|
05d3233c03 | ||
|
|
203b5d925b | ||
|
|
b2f6338b0b | ||
|
|
26e526d5fe | ||
|
|
054d6c58cb | ||
|
|
cc6b5ea0f5 | ||
|
|
495985ace8 | ||
|
|
a304853a7b | ||
|
|
eb2c58f0e9 | ||
|
|
125429bccd | ||
|
|
c5bf1b65ba | ||
|
|
5286a018df | ||
|
|
c408744385 | ||
|
|
5efd5f310f | ||
|
|
e35c14d8e3 | ||
|
|
d49fcc2307 | ||
|
|
afc1dfcf10 | ||
|
|
2dd06ed0c3 | ||
|
|
2ca223cd2c | ||
|
|
f6cf018625 | ||
|
|
1d7806c96e | ||
|
|
650fd892f8 | ||
|
|
83710b14b1 | ||
|
|
1eeb22f579 | ||
|
|
1d70e73106 | ||
|
|
9d93b52be1 | ||
|
|
4431772007 | ||
|
|
f17fafbb48 | ||
|
|
8d30399f33 | ||
|
|
5feb934a4a | ||
|
|
9466b96f69 | ||
|
|
ad00d1b145 | ||
|
|
89ea9ba8a0 | ||
|
|
e08fb3eeb6 | ||
|
|
5800c8910e | ||
|
|
1ec77cb0b5 | ||
|
|
3329514066 | ||
|
|
addc3f322f | ||
|
|
79dcd16d37 | ||
|
|
802c72a9aa | ||
|
|
bf96676df5 | ||
|
|
d245b93196 | ||
|
|
6f96b555e9 | ||
|
|
872700d1be | ||
|
|
1ac08ee1f2 | ||
|
|
ada433150c | ||
|
|
ee40a69e57 | ||
|
|
d6fcaffa99 | ||
|
|
fe4bdda549 | ||
|
|
6a1016a383 | ||
|
|
c53f08d0a7 | ||
|
|
2807ff30e6 | ||
|
|
cf3b9c21dd | ||
|
|
ee616412d6 | ||
|
|
341c6f8722 | ||
|
|
978df99e5c | ||
|
|
a91e2a90b4 | ||
|
|
49c54c6a58 | ||
|
|
6b72163dbe | ||
|
|
93a04c64d2 | ||
|
|
d01d814778 | ||
|
|
af15c256b9 | ||
|
|
dde0056ba2 | ||
|
|
dede5b548c | ||
|
|
7be0831e7c | ||
|
|
660275dc74 | ||
|
|
957e01fbf2 | ||
|
|
b01ffaf763 | ||
|
|
8d6d53a448 | ||
|
|
8f13bfcb4a | ||
|
|
ddf45bf7a7 | ||
|
|
9fabeac7e1 | ||
|
|
758b1f9365 | ||
|
|
0050d34d0e | ||
|
|
fd547383c4 | ||
|
|
edb640f496 | ||
|
|
4569fa3e86 | ||
|
|
33af012352 | ||
|
|
5cc6688d9c | ||
|
|
f8a5f610bb | ||
|
|
877ac8adc7 | ||
|
|
39040c263e | ||
|
|
08759762a1 | ||
|
|
7897fbcf36 | ||
|
|
ce93bb1afb | ||
|
|
3e8085f460 | ||
|
|
2ab201d278 | ||
|
|
ccdac844b9 | ||
|
|
4474939c51 | ||
|
|
f32975ccb0 | ||
|
|
4f92b22955 | ||
|
|
1d8ac0ab45 | ||
|
|
4cfc5258b2 | ||
|
|
14b7415005 | ||
|
|
f482b712ca | ||
|
|
c40a5cbaaa | ||
|
|
9d0efca1e5 | ||
|
|
cf4d76e9a4 | ||
|
|
56569509f9 | ||
|
|
a0904be305 | ||
|
|
7d156fb47b | ||
|
|
e1796d24ad | ||
|
|
2899b1f8eb | ||
|
|
335a033091 | ||
|
|
80aa2367b0 | ||
|
|
e6ca5884a3 | ||
|
|
ef52c84e25 | ||
|
|
478f2fc9ae | ||
|
|
98afededa6 | ||
|
|
7ad2ca9f16 | ||
|
|
03ab379c63 | ||
|
|
bd71efd22b | ||
|
|
87b7a1c759 | ||
|
|
5c59c3b1b8 | ||
|
|
bb98db4568 | ||
|
|
80f0afd600 | ||
|
|
5bc37b51bb | ||
|
|
0f68892d11 | ||
|
|
37e731c2fd | ||
|
|
8312ae30f1 | ||
|
|
28ec42dddf | ||
|
|
d9f572188e | ||
|
|
720a95d472 | ||
|
|
74ba60f4f1 | ||
|
|
e717473a21 | ||
|
|
aca1e26c3b | ||
|
|
0b1bcebc72 | ||
|
|
058a5dca17 | ||
|
|
da21131f9c | ||
|
|
30fd9f84e0 | ||
|
|
291f881e61 | ||
|
|
bdb465b54e | ||
|
|
e39a10da22 | ||
|
|
8f4ab6a7f1 | ||
|
|
3259fc1a52 | ||
|
|
05d85a0dc1 | ||
|
|
e7c4658805 | ||
|
|
51220bcae6 | ||
|
|
e8ff42dd75 | ||
|
|
6bd54ccf2b | ||
|
|
c3bcaba61e | ||
|
|
14693b4a6e | ||
|
|
b1a75df507 | ||
|
|
42d65cf0dd | ||
|
|
b18803e343 | ||
|
|
a36a071dbe | ||
|
|
edc95e1fa0 | ||
|
|
11b3d3ef41 | ||
|
|
64f339b1fa | ||
|
|
a6eefa7afd | ||
|
|
837d1fb43d | ||
|
|
0d190b4ade | ||
|
|
fbc7acea60 | ||
|
|
e6676e1a05 | ||
|
|
b9a56c6c65 | ||
|
|
520c152cc6 | ||
|
|
f7ddf6f872 | ||
|
|
e6997d81c1 | ||
|
|
31923fc73c | ||
|
|
5dbfc97780 | ||
|
|
da525fa1c8 | ||
|
|
372866ee95 | ||
|
|
d1e1df01c0 | ||
|
|
b12d9f352b | ||
|
|
b3dc4b617b | ||
|
|
67e3ff368d | ||
|
|
569ad41894 | ||
|
|
bd5225967d | ||
|
|
9c9df4ee3b | ||
|
|
a859a20062 | ||
|
|
4fd8e736f9 | ||
|
|
41fc9b329b | ||
|
|
3ea9408103 | ||
|
|
589170ac8a | ||
|
|
54bd6cd402 | ||
|
|
9374b56ec7 | ||
|
|
f6a3ea79ef | ||
|
|
840e2610c5 | ||
|
|
d673dbf1cc | ||
|
|
26c5de3122 | ||
|
|
7c5d2647c8 | ||
|
|
3a6256179c | ||
|
|
1d4e5fbd70 | ||
|
|
a456fffe45 | ||
|
|
3240924b2c | ||
|
|
670406991c | ||
|
|
839543bbdd | ||
|
|
47511b33a3 | ||
|
|
264fd0b3a4 | ||
|
|
f10af90c14 | ||
|
|
782a82ea30 | ||
|
|
bc0d6bb09e | ||
|
|
54fd9d9492 | ||
|
|
59f804c96a | ||
|
|
fba1325abe | ||
|
|
4b634ad67a | ||
|
|
3990ba055a | ||
|
|
48784a3d7a | ||
|
|
61e87718da | ||
|
|
a55dd34eda | ||
|
|
fc304e5dc7 | ||
|
|
3870950e29 | ||
|
|
fc97830d22 | ||
|
|
c7579c259b | ||
|
|
1272e09a73 | ||
|
|
79b4cbe7f1 | ||
|
|
9a000f7881 | ||
|
|
30367ad328 | ||
|
|
d67b447895 | ||
|
|
0d9c727dde | ||
|
|
321a56d5cf | ||
|
|
fbbc4ee864 | ||
|
|
36f6e792db | ||
|
|
f71c7c5076 | ||
|
|
33ec5a5cce | ||
|
|
bbcf0add3f | ||
|
|
80aa041d1c | ||
|
|
35617e97a5 | ||
|
|
9a802c0db9 | ||
|
|
d588f2fdee | ||
|
|
ad0424175d | ||
|
|
3bf9856c26 | ||
|
|
2395ee05f9 | ||
|
|
2a5168e800 | ||
|
|
da861cc73c | ||
|
|
4cd974cb0e | ||
|
|
14cb32fc58 | ||
|
|
3197671950 | ||
|
|
cd29baf9cc | ||
|
|
03ca76e679 | ||
|
|
933855baac | ||
|
|
ee61259114 | ||
|
|
d26f88e0f1 | ||
|
|
c8fc144b8a | ||
|
|
d8ae647140 | ||
|
|
ab5767bee8 | ||
|
|
7c63d3c176 | ||
|
|
d837dfdd77 | ||
|
|
31612a7cb1 | ||
|
|
3d25ec32c0 | ||
|
|
e3cda07f0f | ||
|
|
18762961e1 | ||
|
|
9cc1112a3e | ||
|
|
2204a8437f | ||
|
|
c37e0d2fab | ||
|
|
4ce85692a7 | ||
|
|
860658f367 | ||
|
|
4310ce95ea | ||
|
|
66ae6b2a33 | ||
|
|
5f45a38f08 | ||
|
|
c26cd30dfd | ||
|
|
4b0fe94f34 | ||
|
|
7fc80f8e10 | ||
|
|
2875676308 | ||
|
|
814726d545 | ||
|
|
0ae60a0463 | ||
|
|
c96bef9ac5 | ||
|
|
db9fd9e4d5 | ||
|
|
90ab2f0dbd | ||
|
|
f8f6cb3cc2 | ||
|
|
72d741f795 | ||
|
|
e784f4bcf0 | ||
|
|
a50f34d2b4 | ||
|
|
dce58a7793 | ||
|
|
5c9de8de7c | ||
|
|
c1c2ceb33e | ||
|
|
b39eca0e0e | ||
|
|
cadcc69eef | ||
|
|
120f2b2d44 | ||
|
|
495d7ecf19 | ||
|
|
140a45a7cb | ||
|
|
bd12ff73f8 | ||
|
|
7b4182dce3 | ||
|
|
f8098423a3 | ||
|
|
9e1a8c29ac | ||
|
|
157833d806 | ||
|
|
91a61eb014 | ||
|
|
1f037dbda7 | ||
|
|
d9c68e7360 | ||
|
|
51507d3905 | ||
|
|
31e83ebbb5 | ||
|
|
3e2f1fb5a8 | ||
|
|
5ad360eaf2 | ||
|
|
b7b8cd3fea | ||
|
|
243fccac9e | ||
|
|
51b3035434 | ||
|
|
be9fba23ac | ||
|
|
0ac9b703d5 | ||
|
|
21c51ff074 | ||
|
|
a735b2f0b0 | ||
|
|
0baea2c5ea | ||
|
|
ca2463567e | ||
|
|
c950dc5f53 | ||
|
|
5c6a29f2d7 | ||
|
|
6e12a9d22e | ||
|
|
df819853ef | ||
|
|
d8c3fb4190 | ||
|
|
25e5ecfe61 | ||
|
|
809b5bbe18 | ||
|
|
1ec9139307 | ||
|
|
293ea1f6cf | ||
|
|
ed43a47ad7 | ||
|
|
82980639c8 | ||
|
|
01dd493475 | ||
|
|
0b29c8c51b | ||
|
|
7c2f4277c3 | ||
|
|
8c515f542c | ||
|
|
73d80b136f | ||
|
|
93c5cf70a0 | ||
|
|
e2a098c3ff | ||
|
|
c766048224 | ||
|
|
9eb87fae62 | ||
|
|
7f2f4d8e3b | ||
|
|
ce5f9c0f08 | ||
|
|
cf443ed217 | ||
|
|
bde582d8ff | ||
|
|
0a00332580 | ||
|
|
137d7bf240 | ||
|
|
302c7ef455 | ||
|
|
87b54a44c0 | ||
|
|
f9eee0de93 | ||
|
|
1114d2d30d | ||
|
|
65d50d046c | ||
|
|
b2ef957682 | ||
|
|
56be23e304 | ||
|
|
215869037b | ||
|
|
f5d4581911 | ||
|
|
5848efdf67 | ||
|
|
c79277afaf | ||
|
|
59c8e1ae1c | ||
|
|
d50678649a | ||
|
|
c19f440094 | ||
|
|
b60160f2b9 | ||
|
|
e54c4ed435 | ||
|
|
749774cd03 | ||
|
|
7cc609fdc3 | ||
|
|
2cc8bed203 | ||
|
|
175f02a1c3 | ||
|
|
8a8ac0ccde | ||
|
|
168427f24e | ||
|
|
8338518405 | ||
|
|
95783b7ff3 | ||
|
|
f7a4f956d7 | ||
|
|
feea4d4785 | ||
|
|
e267623025 | ||
|
|
5c22583a52 | ||
|
|
7ba93c9742 | ||
|
|
b4b40bf82f | ||
|
|
b96218f305 | ||
|
|
e4013a616f | ||
|
|
6f3ab8f974 | ||
|
|
c47fc61b73 | ||
|
|
01042bbc54 | ||
|
|
e75f8b75c5 | ||
|
|
ae5e77925a | ||
|
|
b1f2a0e057 | ||
|
|
6f61d5aa11 | ||
|
|
9562f06fea | ||
|
|
c6eaf4ae03 | ||
|
|
24c994dfb5 | ||
|
|
f623e5d004 | ||
|
|
9ac03a00d8 | ||
|
|
52b819a1b7 | ||
|
|
3ad32e0fc7 | ||
|
|
18c4a55518 | ||
|
|
0d8a889556 | ||
|
|
25f180a297 | ||
|
|
ae2f2d8294 | ||
|
|
d3ac95d1f6 | ||
|
|
a138c4a1c7 | ||
|
|
0f359e84d6 | ||
|
|
49d73b7d4f | ||
|
|
33dc7b82ea | ||
|
|
4714ea6037 | ||
|
|
c2e5f241b0 | ||
|
|
7f0e299b74 | ||
|
|
e418ccf8e0 | ||
|
|
53e119c0ce | ||
|
|
f934d3b7b1 | ||
|
|
12d5b9f6f6 | ||
|
|
727a5cfbf5 | ||
|
|
6345080769 | ||
|
|
928f3d32cd | ||
|
|
81fd2dc82f | ||
|
|
225918ac52 | ||
|
|
796343bc5e | ||
|
|
cdb86f6c11 | ||
|
|
625e26f01f | ||
|
|
21f0429fd6 | ||
|
|
e7a30048cf | ||
|
|
ed952e19eb | ||
|
|
e4a7bdd0eb | ||
|
|
13e394bbfe | ||
|
|
a6b95064d9 | ||
|
|
c30a556bdc | ||
|
|
2bebba740e | ||
|
|
57dfb02689 | ||
|
|
7931d8b26e | ||
|
|
434d50b2d1 | ||
|
|
fad905a426 | ||
|
|
475c3b690b | ||
|
|
88eae3cb9a | ||
|
|
0021c7233c | ||
|
|
725224b6c7 | ||
|
|
28f399079d | ||
|
|
3e0d594e29 | ||
|
|
9092b1f9b0 | ||
|
|
4ac30ff7b2 | ||
|
|
803d3395d6 | ||
|
|
51288231cd | ||
|
|
a44cd528ef | ||
|
|
cfbe0b031e | ||
|
|
bf7fb1620e | ||
|
|
50056fd301 | ||
|
|
927b134996 | ||
|
|
9d66619cda | ||
|
|
c9670536cc | ||
|
|
731518a4fc | ||
|
|
764df316a7 | ||
|
|
1ecfdafbe8 | ||
|
|
4b09aeefd6 | ||
|
|
71e407c3a5 | ||
|
|
48c4e7432d | ||
|
|
202194c16f | ||
|
|
0af5713483 | ||
|
|
3b6a5c353c | ||
|
|
e6d8f34db0 | ||
|
|
ef97a0d184 | ||
|
|
40866754d1 | ||
|
|
d5fb7d094c | ||
|
|
48b861054c | ||
|
|
568b7541a4 | ||
|
|
914a6e6a57 | ||
|
|
d1e737dc96 | ||
|
|
ae68a05f87 | ||
|
|
79e6f3ef1c | ||
|
|
5f137f4055 | ||
|
|
4f5215a312 | ||
|
|
06ea8f6cb1 | ||
|
|
2e2890d8d3 | ||
|
|
a07ef6c273 | ||
|
|
a02acc9c67 | ||
|
|
bc5e61b04b | ||
|
|
665036189d | ||
|
|
8fe72e7f2e | ||
|
|
4a373015de | ||
|
|
31ad540286 | ||
|
|
448faebfad | ||
|
|
3b7f25071e | ||
|
|
c661395bb0 | ||
|
|
c231220b42 | ||
|
|
1c6e5bec73 | ||
|
|
8919aaad94 | ||
|
|
ff6a2fe734 | ||
|
|
5ca4f7e411 | ||
|
|
2c105718b0 | ||
|
|
9cc471f66f | ||
|
|
e18867c938 | ||
|
|
27bd942c4c | ||
|
|
234ca72e73 | ||
|
|
f35a09a34d | ||
|
|
fa6d06a4ac | ||
|
|
ec6daeeadb | ||
|
|
f24f25f071 | ||
|
|
dbf0e3af9f | ||
|
|
6ecb607c0b | ||
|
|
1bf6b00be0 | ||
|
|
4b4f52e39a | ||
|
|
1170efbe02 | ||
|
|
3e86e73fed | ||
|
|
dd43861572 | ||
|
|
61a19ca9e0 | ||
|
|
d02af31e7c | ||
|
|
ab7b88b67b | ||
|
|
b80c816c5a | ||
|
|
c0ad6f49e7 | ||
|
|
91a56a8c2c | ||
|
|
135a473dc5 | ||
|
|
a8b53b07e4 | ||
|
|
171cbd4497 | ||
|
|
a1383612bc | ||
|
|
fe798c19f3 | ||
|
|
ead3add4cc | ||
|
|
db13c2d1c5 | ||
|
|
f2daab4b80 | ||
|
|
6fd02c83ab | ||
|
|
c288a0503a | ||
|
|
2e2b65a4bb | ||
|
|
982d0a53f9 | ||
|
|
f89ecfb595 | ||
|
|
bddb96a5fa | ||
|
|
39b29d12e4 | ||
|
|
6cb1e2c06d | ||
|
|
d932a9a862 | ||
|
|
341b552d0b | ||
|
|
1586bd9768 | ||
|
|
e8dde3a756 | ||
|
|
bb591bf56d | ||
|
|
8cf83b276a | ||
|
|
a1547a7288 | ||
|
|
0722403a23 | ||
|
|
cd4a91d975 | ||
|
|
c7870b4e9d | ||
|
|
239dae5a65 | ||
|
|
13c89d18d7 | ||
|
|
c5dc42eafe | ||
|
|
59b3faab1e | ||
|
|
b88beb5002 | ||
|
|
9fea35c25d | ||
|
|
372013bcc6 | ||
|
|
20ca58b570 | ||
|
|
9c4cca20fb | ||
|
|
6b8536a86e | ||
|
|
3b41857637 | ||
|
|
fa78ac2157 | ||
|
|
0a0bb9c83c | ||
|
|
36834a039e | ||
|
|
4960c898bb | ||
|
|
af61edbc5c | ||
|
|
237bfc08a1 | ||
|
|
032e9a089d | ||
|
|
ba83779dde | ||
|
|
267d72f844 | ||
|
|
b533cdb89f | ||
|
|
3f118ac76b | ||
|
|
08a8f0097c | ||
|
|
c7a3599e46 | ||
|
|
62f80baa69 | ||
|
|
09dc8cd437 | ||
|
|
c613ba401b | ||
|
|
866df0b5cf | ||
|
|
8f2b56ba05 | ||
|
|
2049885f11 | ||
|
|
4caef337d5 | ||
|
|
a644792bf6 | ||
|
|
54c7f8fc6a | ||
|
|
2e59b87bbe | ||
|
|
f89242ad8f | ||
|
|
845903788c | ||
|
|
53a9047c20 | ||
|
|
a2b74bbc30 | ||
|
|
50ef93cc45 | ||
|
|
e2a5da9c14 | ||
|
|
bf31116d0d | ||
|
|
d79fa8c4f3 | ||
|
|
87e0a7bea3 | ||
|
|
b34cabd55c | ||
|
|
704bd36c77 | ||
|
|
5c2c4d0f4b | ||
|
|
80cca5cb34 | ||
|
|
dcd603f2f5 | ||
|
|
1bee3b49bf | ||
|
|
11bcccb8f9 | ||
|
|
7425493706 | ||
|
|
dca0cfd424 | ||
|
|
21314def89 | ||
|
|
90e544d279 | ||
|
|
89e21558a1 | ||
|
|
b28cc51457 | ||
|
|
2d0a203b5f | ||
|
|
2948666c1d | ||
|
|
b2a2659546 | ||
|
|
e2eb7102c9 | ||
|
|
56301e653b | ||
|
|
1d825e08d2 | ||
|
|
c81a27709c | ||
|
|
2b143172f8 | ||
|
|
84330ec6eb | ||
|
|
0bd703de7f | ||
|
|
e6a55b7d5c | ||
|
|
b6ec7631d2 | ||
|
|
5abbd00d63 | ||
|
|
515c94976e | ||
|
|
912adad8f6 | ||
|
|
70fdd193a3 | ||
|
|
9d83e968fc | ||
|
|
04329da133 | ||
|
|
19ccfbb1a6 | ||
|
|
c53ab09abc | ||
|
|
a4d8daac1d | ||
|
|
99d566d521 | ||
|
|
aa37f73161 | ||
|
|
087518378d | ||
|
|
913d27929b | ||
|
|
530d39bec5 | ||
|
|
cf70dc3845 | ||
|
|
d1421fee3f | ||
|
|
3ec490b36e | ||
|
|
ee42685179 | ||
|
|
e02d687771 | ||
|
|
dddb592253 | ||
|
|
d0ad88d317 | ||
|
|
3d3ce0151f | ||
|
|
f017b0e562 | ||
|
|
4ba2bb669b | ||
|
|
9fc2424736 | ||
|
|
980c01ea70 | ||
|
|
758edcaf5c | ||
|
|
950f825e31 | ||
|
|
0a985f5c78 | ||
|
|
58353f4e6d | ||
|
|
c3d2b5c4b0 | ||
|
|
c2764b4b8a | ||
|
|
9bc8ad0d23 | ||
|
|
488c3540a8 | ||
|
|
1d03950a64 | ||
|
|
e24ea7df2e | ||
|
|
fe5e3d6405 | ||
|
|
db8584e40b | ||
|
|
2e4db38d1a | ||
|
|
f82470c1b1 | ||
|
|
650cc93fc0 | ||
|
|
e61070307a | ||
|
|
2fa46867e5 | ||
|
|
636b2e760a | ||
|
|
8cecf46310 | ||
|
|
a7bcda7eba | ||
|
|
a610dd2c70 | ||
|
|
df98c77a48 | ||
|
|
331dc33c86 | ||
|
|
8493b37171 | ||
|
|
2449c46165 | ||
|
|
a642c2d344 | ||
|
|
88c7b988e7 | ||
|
|
36651260c6 | ||
|
|
6c0072dc54 | ||
|
|
e38f5a77c0 | ||
|
|
6443926a79 | ||
|
|
489deb9ee6 | ||
|
|
a2039110e0 | ||
|
|
f1666b64ee | ||
|
|
d7e419c88b | ||
|
|
de8f8053be | ||
|
|
7aadef5c21 | ||
|
|
f8384dfbdf | ||
|
|
b91cbf827c | ||
|
|
096619d9f7 | ||
|
|
7b8006406c | ||
|
|
90483d549a | ||
|
|
5473055c1c | ||
|
|
f3b082b8e1 | ||
|
|
52fe99a080 | ||
|
|
68b1e941d1 | ||
|
|
109fce9b1a | ||
|
|
014d73dd3f | ||
|
|
ad82744e91 | ||
|
|
638f66ade4 | ||
|
|
c190efa7c4 | ||
|
|
b071eb3366 | ||
|
|
a7c7a86a90 | ||
|
|
0c21519cf6 | ||
|
|
d7f805b26e | ||
|
|
4f702feab3 | ||
|
|
46175ceb1f | ||
|
|
c206860736 | ||
|
|
b97c222490 | ||
|
|
53ad5e3cec | ||
|
|
e67ce8dbf4 | ||
|
|
ac661be5c4 | ||
|
|
ab4e386491 | ||
|
|
aa125b535e | ||
|
|
29df3bb5e2 | ||
|
|
80ee30a532 | ||
|
|
a20b1bcd89 | ||
|
|
4269abd2ca | ||
|
|
a73397065c | ||
|
|
5805a6c5a3 | ||
|
|
e813077c92 | ||
|
|
b1ae68e8c2 | ||
|
|
b6162b2d4a | ||
|
|
0da7854f7f | ||
|
|
a073747e66 | ||
|
|
0975e31371 | ||
|
|
a1231f1708 | ||
|
|
3581da0fe7 | ||
|
|
531810e633 | ||
|
|
36a1db78e2 | ||
|
|
0e58009ced | ||
|
|
ecdcc6894b | ||
|
|
677a2f9396 | ||
|
|
b14f30d4d1 | ||
|
|
4396df55ae | ||
|
|
de6ce93a31 | ||
|
|
b449b7cdfc | ||
|
|
de4412fcff | ||
|
|
8e9b34125d | ||
|
|
024b3175fa | ||
|
|
9fadc6df3b | ||
|
|
f51f8f0f2d | ||
|
|
d805df4887 | ||
|
|
d161c6c074 | ||
|
|
6603c9e682 | ||
|
|
157360c26c | ||
|
|
e16199b360 | ||
|
|
df42424970 | ||
|
|
22ce6ce75e | ||
|
|
e8dde142d2 | ||
|
|
44fe272ce5 | ||
|
|
23a8f32910 | ||
|
|
d523c0408e | ||
|
|
0c2edba316 | ||
|
|
7cfc643522 | ||
|
|
98b446efd2 | ||
|
|
bbf9aaed64 | ||
|
|
13d36457af | ||
|
|
4f850b0f2e | ||
|
|
7118e0d9d3 | ||
|
|
900edef407 | ||
|
|
a7e4b99ce6 | ||
|
|
0463667270 | ||
|
|
d179bc20ae | ||
|
|
bc658a9223 | ||
|
|
e32e3baf3a | ||
|
|
4436f4cf42 | ||
|
|
1dc7b40ee9 | ||
|
|
fb2221d281 | ||
|
|
b5a295970b | ||
|
|
485a453b27 | ||
|
|
03d9ba9579 | ||
|
|
ead7adea31 | ||
|
|
9793fdfc06 | ||
|
|
60b73e5c0d | ||
|
|
ed374c83d1 | ||
|
|
f53828e01a | ||
|
|
91ab50fa6a | ||
|
|
bee0d9d086 | ||
|
|
b38f09f118 | ||
|
|
65f57e8bf1 | ||
|
|
e424e42e05 | ||
|
|
a22cd5338d | ||
|
|
f921737d1c | ||
|
|
2f760afc70 | ||
|
|
4ce56433f9 | ||
|
|
f8ace5fbb5 | ||
|
|
f89778e969 | ||
|
|
99f74d3824 | ||
|
|
dec9c42edc | ||
|
|
34365c13cb | ||
|
|
8a4c780fb1 | ||
|
|
bed49408da | ||
|
|
5dd400bdc5 | ||
|
|
07d5a03a95 | ||
|
|
d53e7665d7 | ||
|
|
4021d7cbac | ||
|
|
2634ca5f28 | ||
|
|
aa887fe27b | ||
|
|
c9077e1254 | ||
|
|
fcf5636d9a | ||
|
|
1c4713ef1a | ||
|
|
d48da82f55 | ||
|
|
7128ab26d8 | ||
|
|
58155041d1 | ||
|
|
e96453eac9 | ||
|
|
a1d98c54ad | ||
|
|
fd423481cb | ||
|
|
48da7d0c2e | ||
|
|
949577fb07 | ||
|
|
dc876efff8 | ||
|
|
c39b650e9b | ||
|
|
12c1cb871d | ||
|
|
c75e86812d | ||
|
|
cc1fffbe8b | ||
|
|
e6313a5e52 | ||
|
|
2a278dcff9 | ||
|
|
8539529061 | ||
|
|
5a4b7d93c0 | ||
|
|
5e459effc0 | ||
|
|
5f0cf2ecb5 | ||
|
|
44c133a68c | ||
|
|
f842f15cd4 | ||
|
|
04c78a37d7 | ||
|
|
cba4dbdf84 | ||
|
|
2fbb162bbf | ||
|
|
a1be962997 | ||
|
|
a3c3f5d990 | ||
|
|
c785d4fdca | ||
|
|
6b78707bd8 | ||
|
|
a1565ed481 | ||
|
|
ae056b312f | ||
|
|
b20c1158b0 | ||
|
|
210f567b74 | ||
|
|
82b91c7bd9 | ||
|
|
5168a99c3b | ||
|
|
e437917841 | ||
|
|
6543440926 | ||
|
|
30c53c6048 | ||
|
|
87412eb4ff | ||
|
|
f1ad5fc57d | ||
|
|
dd3a457ec0 | ||
|
|
069f2aba8b | ||
|
|
9e19553150 | ||
|
|
2d7b009442 | ||
|
|
68817e3595 | ||
|
|
3e229d6b82 | ||
|
|
5fce471577 | ||
|
|
68b1e7cb16 | ||
|
|
761e02ff6e | ||
|
|
5fe4408631 | ||
|
|
bae0685ac1 | ||
|
|
6322a0c045 | ||
|
|
0b89de905e | ||
|
|
49c4453678 | ||
|
|
c056cafda6 | ||
|
|
b375c24340 | ||
|
|
5a76131f24 | ||
|
|
764769a6ca | ||
|
|
b0226df3ab | ||
|
|
8c86120b33 | ||
|
|
8805d0666b | ||
|
|
ae259a894e | ||
|
|
88d3c94c26 | ||
|
|
93dd0e1c72 | ||
|
|
af1a762048 | ||
|
|
980daa6f64 | ||
|
|
814ba371cb | ||
|
|
027e1c7330 | ||
|
|
585af9656b | ||
|
|
101e84d837 | ||
|
|
c0de6137b0 | ||
|
|
eb93a5ae2d | ||
|
|
77124cd922 | ||
|
|
3b5c844743 | ||
|
|
8e909011a0 | ||
|
|
9607720a5d | ||
|
|
95ac332c85 | ||
|
|
df615f1aba | ||
|
|
1a60e097f2 | ||
|
|
ebcd758e68 | ||
|
|
15201af923 | ||
|
|
e4b43abf11 | ||
|
|
f63cccdb0c | ||
|
|
b5fd465788 | ||
|
|
6c2d05742d | ||
|
|
838422b568 | ||
|
|
4ca327d3d9 | ||
|
|
f5dce3db0a | ||
|
|
aee7de283a | ||
|
|
7d9c65cb41 | ||
|
|
6140b11185 | ||
|
|
b8394d2d9a | ||
|
|
d615f0a5ab | ||
|
|
d2249f6788 | ||
|
|
7a7707f712 | ||
|
|
54af3d4a4a | ||
|
|
e5fd46a3fa | ||
|
|
26e5cb6f14 | ||
|
|
2c01f64a06 | ||
|
|
79654c344c | ||
|
|
ed9a17320c | ||
|
|
093b795407 | ||
|
|
cf0b0ed8d2 | ||
|
|
53503883d1 | ||
|
|
b4eaf9ff1d | ||
|
|
5f286322cb | ||
|
|
a1e938626b | ||
|
|
13c0fb769d | ||
|
|
67f92c9e4c | ||
|
|
f438627063 | ||
|
|
252458b6d5 | ||
|
|
5be109f13c | ||
|
|
0ab6691ea6 | ||
|
|
111b105466 | ||
|
|
1dba805c95 | ||
|
|
a691d69011 | ||
|
|
83e645c1d9 | ||
|
|
1090fbb390 | ||
|
|
7669fec40a | ||
|
|
a8ee2a47cb | ||
|
|
3a159159b0 | ||
|
|
3827fcd306 | ||
|
|
1a925b407d | ||
|
|
568b6bd14d | ||
|
|
94106404ad | ||
|
|
07d5a52004 | ||
|
|
60e59c2c9d | ||
|
|
d9ab1cf2c8 | ||
|
|
3ab85750ed | ||
|
|
13a98f8618 | ||
|
|
4cf64beae0 | ||
|
|
28bba220ad | ||
|
|
c71f7645a5 | ||
|
|
6537c6fcde | ||
|
|
7165c3941a | ||
|
|
31969aa076 | ||
|
|
7e098cd7d9 | ||
|
|
8850812994 | ||
|
|
657d03aaf8 | ||
|
|
cb293f5f9a | ||
|
|
388290a90f | ||
|
|
218e5964c3 | ||
|
|
6a9ec46488 | ||
|
|
4577fa5d40 | ||
|
|
d571f7de9e | ||
|
|
408cf2ff08 | ||
|
|
4be47b175a | ||
|
|
d339e41210 | ||
|
|
dca7d229ba | ||
|
|
edb16c3b46 | ||
|
|
f2a5461e15 | ||
|
|
7d325ade95 | ||
|
|
669924fa2a | ||
|
|
480af187ac | ||
|
|
ceb18d5f1d | ||
|
|
93ae2b970b | ||
|
|
e55a389d60 | ||
|
|
5ff2524e6e | ||
|
|
35619744b4 | ||
|
|
c47e6dc32e | ||
|
|
3f04b185f6 | ||
|
|
61818099a1 | ||
|
|
1bca58ffda | ||
|
|
cf506d0f57 | ||
|
|
4b16cf5463 | ||
|
|
46be09df53 | ||
|
|
0e564f01d1 | ||
|
|
9326231d21 | ||
|
|
0bd3098fc9 | ||
|
|
eb5286756d | ||
|
|
116b8d45c1 | ||
|
|
bff4c83f30 | ||
|
|
be9316de10 | ||
|
|
98d87a9e44 | ||
|
|
ac3e1cc524 | ||
|
|
18c8260a95 | ||
|
|
18b88bb6f7 | ||
|
|
a90383c79e | ||
|
|
144b47ec78 | ||
|
|
8cefe8a179 | ||
|
|
d3f15538e3 | ||
|
|
759a4929de | ||
|
|
eca1c5a920 | ||
|
|
03fce279e7 | ||
|
|
1d225022b4 | ||
|
|
d5db4da02e | ||
|
|
553595a0b4 | ||
|
|
3aff395370 | ||
|
|
4e71189bd8 | ||
|
|
81eb4b1a19 | ||
|
|
a640bcaa87 | ||
|
|
be24019cd5 | ||
|
|
7be675d101 | ||
|
|
c6b39a9650 | ||
|
|
5ac1ad0975 | ||
|
|
69f4744640 | ||
|
|
46a34dd765 | ||
|
|
c2a68ef8ad | ||
|
|
75dc5db149 | ||
|
|
81a483c74b | ||
|
|
9f3995198b | ||
|
|
18f0384fd1 | ||
|
|
87d43c08fe | ||
|
|
2f0c8cccd6 | ||
|
|
d8796b291a | ||
|
|
9a321e4106 | ||
|
|
0e7a445fbe | ||
|
|
84dd4b0571 | ||
|
|
12f5c4b33d | ||
|
|
0ab9063d30 | ||
|
|
9981f1c64c | ||
|
|
c0768a40af | ||
|
|
af343c80c0 | ||
|
|
29c6035fb8 | ||
|
|
bd3b8c3fc3 | ||
|
|
2742a12de1 | ||
|
|
b49e38c19b | ||
|
|
357436a3ca | ||
|
|
a0bbc9cdd7 | ||
|
|
0501bb5c33 | ||
|
|
def58303fa | ||
|
|
d57b03a1cd | ||
|
|
e3c30d3b3e | ||
|
|
5671f80cf5 | ||
|
|
d7714f4145 |
No files matched your search
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Server version**
|
||||
|
||||
|
||||
**Plugin version** ("latest" is not a version)
|
||||
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
|
||||
**How to reproduce**
|
||||
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
|
||||
**Actual behavior**
|
||||
|
||||
|
||||
**Additional info** (errors, screenshots, ...)
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Bug Report
|
||||
description: Unwanted behavior that does not match configuration.
|
||||
labels: ["Bug"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: How the plugin behaves
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: How the plugin is supposed to behave instead with your configuration.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: What to do in order to reproduce this issue
|
||||
placeholder: |
|
||||
Step 1: ..
|
||||
Step 2: ..
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Any other info you want to provide, such as screenshots.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have read the wiki to make sure it's not an issue with configuration
|
||||
required: true
|
||||
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
|
||||
required: true
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Error
|
||||
description: Error message with a stack trace in console or error file.
|
||||
labels: ["Error"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Stack trace
|
||||
description: The error message and stack trace you received in console or errors.log file.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce (if known)
|
||||
description: What to do in order to reproduce this issue
|
||||
placeholder: |
|
||||
Step 1: ..
|
||||
Step 2: ..
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Any other info you want to provide.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have included a paste of the error
|
||||
required: true
|
||||
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
|
||||
required: true
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Wiki change
|
||||
description: Change to text on a wiki page or adding a new section.
|
||||
labels: ["Wiki"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Page link
|
||||
description: Link to the page that should receive the change.
|
||||
placeholder: https://github.com/NEZNAMY/TAB/wiki/Installation
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current text (if applicable)
|
||||
description: Current version of the text (if modifying), ignore in case of new section.
|
||||
placeholder: |
|
||||
Installation on Bukkit/Spigot/Paper is as simple as it can be. Just put the plugin into plugins folder and restart the server. You can also run `/plugman load tab` to load the plugin while server is running.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: New text
|
||||
description: What the text should be changed to / how should new section look
|
||||
placeholder: |
|
||||
Put the plugin into plugins folder and restart the server. In case you don't want to restart your server, loading with plugins like PlugMan is supported.
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1,44 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v3
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: "${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}"
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Build and analyze
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
run: ./gradlew clean build
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
with:
|
||||
name: Jar
|
||||
path: jar/build/libs/TAB-**.jar
|
||||
+10
-5
@@ -1,8 +1,7 @@
|
||||
# Compiled class file
|
||||
*.class
|
||||
/bin/
|
||||
/target/
|
||||
/src/test/
|
||||
bin/
|
||||
target/
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
@@ -31,8 +30,14 @@ hs_err_pid*
|
||||
.project
|
||||
|
||||
# intellij
|
||||
/.idea/
|
||||
.idea/
|
||||
|
||||
# other
|
||||
.gitattributes
|
||||
*.iml
|
||||
*.iml
|
||||
run/
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,43 +1,48 @@
|
||||
## Introduction
|
||||
Here it is. After countless requests to go opensource, I did it to make those people quiet. It worked.
|
||||
It's already been a year and I still can't see any positives of it. This way I just made it easier for everyone to make a plugin as good, so people can stop using it and switch to something better.
|
||||
|
||||
|
||||
## Compiling
|
||||
Plugin can be compiled using maven.
|
||||
# About
|
||||
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
|
||||
similar plugins in terms of features, performance, compatibility and support.
|
||||
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
|
||||
|
||||
|
||||
## Premium code
|
||||
The source contains content of premium version as well. However, this must be activated by setting [this line to return true](https://github.com/NEZNAMY/TAB/blob/master/src/main/java/me/neznamy/tab/shared/TAB.java#L79).
|
||||
|
||||
|
||||
## Links
|
||||
Discord: https://discord.gg/EaSvdk6
|
||||
SpigotMC (free): https://www.spigotmc.org/resources/57806/
|
||||
MC-Market (paid): https://www.mc-market.org/resources/14009/
|
||||
Wiki: https://github.com/NEZNAMY/TAB/wiki
|
||||
|
||||
|
||||
## Downloads
|
||||
Official release: https://www.spigotmc.org/resources/57806/
|
||||
Pre-releases: https://github.com/NEZNAMY/TAB/releases
|
||||
|
||||
|
||||
## Maven Repository
|
||||
```
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
# Distribution
|
||||
As of TAB 3.0.0, released with MC 1.18 (November 30th, 2021), there is no free / premium
|
||||
version anymore. Only one version of the plugin exists now, which contains all implemented features
|
||||
and is available to everyone for free. However, official support on discord is only provided to
|
||||
those who have purchased the plugin.
|
||||
|
||||
<dependencies>
|
||||
<!-- TAB -->
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>2.8.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
# Compiling
|
||||
Compilation requires JDK 8 and up.
|
||||
To compile the plugin, run `./gradlew build` from the terminal.
|
||||
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
|
||||
|
||||
# Links
|
||||
The precompiled JAR can be downloaded for free from:
|
||||
* Releases: https://github.com/NEZNAMY/TAB/releases
|
||||
* MC-Market: https://www.mc-market.org/resources/20631/
|
||||
|
||||
Supporting my work:
|
||||
* MC-Market: https://www.mc-market.org/resources/14009/
|
||||
* Polymart: https://polymart.org/resource/484
|
||||
* Donation link: https://paypal.me/neznamy1
|
||||
|
||||
Other links:
|
||||
* Discord: https://discord.gg/EaSvdk6
|
||||
* Wiki: https://github.com/NEZNAMY/TAB/wiki
|
||||
|
||||
# Issues
|
||||
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
|
||||
In other words, they **are not available for**:
|
||||
* **Support** - support is provided on discord for buyers, where roles are given to verified users
|
||||
allowing us to know who bought it and who didn't. It also serves as a much faster way of communication.
|
||||
* **Feature requests** - closed indefinitely.
|
||||
|
||||
# Contributing
|
||||
If this plugin has helped you in any way, and you would like to return something back
|
||||
to make the plugin even better, there is a lot of ways to contribute:
|
||||
* Open a **pull request** containing a new feature or a bug fix, which you believe many users will benefit from.
|
||||
* Make detailed high-quality **bug reports**. The difference between a bug getting fixed in 1 week vs 1 hour
|
||||
is in quality of the report (typically providing correct steps to reproduce that actually work).
|
||||
* Test **experimental builds**, which can be found in [Github Actions](https://github.com/NEZNAMY/TAB/actions) or in a form of pre-releases on discord
|
||||
for problems of any kind including bugs. This way you will help make the actual releases more stable / help get stable releases sooner.
|
||||
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
|
||||
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples of usage of functions.
|
||||
* [Buy a copy](https://www.mc-market.org/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
|
||||
@@ -0,0 +1,9 @@
|
||||
dependencies {
|
||||
compileOnlyApi(libs.netty)
|
||||
api(libs.jsonSimple) {
|
||||
exclude("junit", "junit")
|
||||
}
|
||||
compileOnlyApi(libs.snakeyaml)
|
||||
api(libs.yamlAssist)
|
||||
compileOnlyApi(libs.gson)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
public interface ArmorStand {
|
||||
|
||||
/**
|
||||
* Returns true if offset is static, false if not
|
||||
* @return true if static, false if dynamic
|
||||
*/
|
||||
boolean hasStaticOffset();
|
||||
|
||||
/**
|
||||
* Changes offset of the armor stand
|
||||
* @param offset - new offset
|
||||
*/
|
||||
void setOffset(double offset);
|
||||
|
||||
/**
|
||||
* Returns offset of the armor stand
|
||||
* @return offset from base
|
||||
*/
|
||||
double getOffset();
|
||||
|
||||
/**
|
||||
* Returns property for armor stand's name
|
||||
* @return property for armor stand's name
|
||||
*/
|
||||
Property getProperty();
|
||||
|
||||
/**
|
||||
* Teleports armor stand to owner's current location for everyone in range
|
||||
*/
|
||||
void teleport();
|
||||
|
||||
/**
|
||||
* Teleports armor stand to owner's current location for specified player
|
||||
* @param viewer - player to send packet to
|
||||
*/
|
||||
void teleport(TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Changes sneaking flag and sends packets to everyone in range
|
||||
* @param sneaking
|
||||
* new sneaking status
|
||||
*/
|
||||
void sneak(boolean sneaking);
|
||||
|
||||
/**
|
||||
* DeSpawns armor stand for everyone
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* DeSpawns armor stand for specified player
|
||||
* @param viewer - player to send packet to
|
||||
*/
|
||||
void destroy(TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Updates armor stand's name if needed
|
||||
*/
|
||||
void refresh();
|
||||
|
||||
/**
|
||||
* Updates visibility if needed
|
||||
* @param force - if refresh should be forced
|
||||
*/
|
||||
void updateVisibility(boolean force);
|
||||
|
||||
/**
|
||||
* Returns entity ID of this armor stand
|
||||
* @return entity ID of this armor stand
|
||||
*/
|
||||
int getEntityId();
|
||||
|
||||
/**
|
||||
* Spawns armor stand for specified player
|
||||
* @param viewer - player to spawn for
|
||||
*/
|
||||
void spawn(TabPlayer viewer);
|
||||
|
||||
void respawn(TabPlayer viewer);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
/**
|
||||
* A helper class for easy management of armor stands of a player
|
||||
*/
|
||||
public interface ArmorStandManager {
|
||||
|
||||
/**
|
||||
* Sends destroy packet of all armor stands to everyone and clears nearby players list
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
void refresh(boolean force);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
public interface FeatureManager {
|
||||
|
||||
/**
|
||||
* Registers a feature, which will start receiving events
|
||||
*
|
||||
* @param name - name of feature
|
||||
* @param feature - the handler
|
||||
*/
|
||||
void registerFeature(String name, TabFeature feature);
|
||||
|
||||
/**
|
||||
* Unregisters feature making it no longer receive events. This does not run unload method nor cancel
|
||||
* tasks created by the feature
|
||||
* @param name - feature name defined in registerFeature
|
||||
*/
|
||||
void unregisterFeature(String name);
|
||||
|
||||
/**
|
||||
* Returns whether a feature with said name is registered or not
|
||||
*
|
||||
* @param name - name of feature defined in registerFeature method
|
||||
* @return true if feature exists, false if not
|
||||
*/
|
||||
boolean isFeatureEnabled(String name);
|
||||
|
||||
/**
|
||||
* Returns feature handler by its name
|
||||
*
|
||||
* @param name - name of feature defined in registerFeature method
|
||||
* @return the feature or null if feature does not exist
|
||||
*/
|
||||
TabFeature getFeature(String name);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
public interface HeaderFooterManager {
|
||||
|
||||
void setHeader(TabPlayer player, String header);
|
||||
|
||||
void setFooter(TabPlayer player, String footer);
|
||||
|
||||
void setHeaderAndFooter(TabPlayer player, String header, String footer);
|
||||
|
||||
void resetHeader(TabPlayer player);
|
||||
|
||||
void resetFooter(TabPlayer player);
|
||||
|
||||
void resetHeaderAndFooter(TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
public interface Property {
|
||||
|
||||
/**
|
||||
* Returns original raw value ignoring API calls
|
||||
* @return original raw value
|
||||
*/
|
||||
String getOriginalRawValue();
|
||||
|
||||
/**
|
||||
* Temporarily overrides current raw value with an API call
|
||||
* @param temporaryValue - temporary value to be assigned
|
||||
*/
|
||||
void setTemporaryValue(String temporaryValue);
|
||||
|
||||
/**
|
||||
* Returns current temporary value
|
||||
* @return temporary value or null if not set
|
||||
*/
|
||||
String getTemporaryValue();
|
||||
|
||||
/**
|
||||
* Returns temporary value (via API) if present, raw value otherwise
|
||||
* @return current raw value
|
||||
*/
|
||||
String getCurrentRawValue();
|
||||
|
||||
/**
|
||||
* Returns last known value
|
||||
* @return last known value
|
||||
*/
|
||||
String get();
|
||||
|
||||
/**
|
||||
* Replaces all placeholders in current raw value, colorizes it, removes remove-strings and returns whether value changed or not
|
||||
* @return if updating changed value or not
|
||||
*/
|
||||
boolean update();
|
||||
|
||||
/**
|
||||
* Replaces all placeholders in current raw value, colorizes it, removes remove-strings and returns it.
|
||||
* Equal to calling update() and then get().
|
||||
* @return updated value
|
||||
*/
|
||||
String updateAndGet();
|
||||
|
||||
/**
|
||||
* Returns value for defined viewer by applying relational placeholders to last known value
|
||||
* @param viewer - the viewer
|
||||
* @return format for the viewer
|
||||
*/
|
||||
String getFormat(TabPlayer viewer);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public interface PropertyConfiguration {
|
||||
|
||||
void setProperty(String name, String property, String server, String world, String value);
|
||||
|
||||
String[] getProperty(String name, String property, String server, String world);
|
||||
|
||||
void remove(String name);
|
||||
|
||||
Map<String, String> getGlobalSettings(String name);
|
||||
|
||||
Map<String, Map<String, String>> getPerWorldSettings(String name);
|
||||
|
||||
Map<String, Map<String, String>> getPerServerSettings(String name);
|
||||
|
||||
Set<String> getAllEntries();
|
||||
|
||||
default Map<String, Map<String, String>> convertMap(Map<String, Map<String, Map<String, String>>> map, String key) {
|
||||
Map<String, Map<String, String>> converted = new HashMap<>();
|
||||
for (Map.Entry<String, Map<String, Map<String, String>>> entry : map.entrySet()) {
|
||||
converted.put(entry.getKey(), entry.getValue().get(key));
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
/**
|
||||
* Enum class representing all possibly used protocol versions
|
||||
*/
|
||||
public enum ProtocolVersion {
|
||||
|
||||
UNKNOWN_SERVER_VERSION ("Unknown"),
|
||||
UNKNOWN_CLIENT_VERSION ("Unknown"),
|
||||
PROXY ("Proxy"),
|
||||
V1_18_2 (758),
|
||||
V1_18_1 (757),
|
||||
V1_18 (757),
|
||||
V1_17_1 (756),
|
||||
V1_17 (755),
|
||||
V1_16_5 (754),
|
||||
V1_16_4 (754),
|
||||
V1_16_3 (753),
|
||||
V1_16_2 (751),
|
||||
V1_16_1 (736),
|
||||
V1_16 (735),
|
||||
V1_15_2 (578),
|
||||
V1_15_1 (575),
|
||||
V1_15 (573),
|
||||
V1_14_4 (498),
|
||||
V1_14_3 (490),
|
||||
V1_14_2 (485),
|
||||
V1_14_1 (480),
|
||||
V1_14 (477),
|
||||
V1_13_2 (404),
|
||||
V1_13_1 (401),
|
||||
V1_13 (393),
|
||||
V1_12_2 (340),
|
||||
V1_12_1 (338),
|
||||
V1_12 (335),
|
||||
V1_11_2 (316),
|
||||
V1_11_1 (316),
|
||||
V1_11 (315),
|
||||
V1_10_2 (210),
|
||||
V1_10_1 (210),
|
||||
V1_10 (210),
|
||||
V1_9_4 (110),
|
||||
V1_9_3 (110),
|
||||
V1_9_2 (109),
|
||||
V1_9_1 (108),
|
||||
V1_9 (107),
|
||||
V1_8 (47),
|
||||
V1_7_10 (5),
|
||||
V1_7_9 (5),
|
||||
V1_7_8 (5),
|
||||
V1_7_7 (5),
|
||||
V1_7_6 (5),
|
||||
V1_7_5 (4),
|
||||
V1_7_4 (4),
|
||||
V1_7_2 (4),
|
||||
V1_6_4 (78),
|
||||
V1_6_2 (74),
|
||||
V1_6_1 (73),
|
||||
V1_5_2 (61),
|
||||
V1_5_1 (60),
|
||||
V1_5 (60),
|
||||
V1_4_7 (51),
|
||||
V1_4_6 (51);
|
||||
|
||||
/** Value array to iterate over to avoid array creations on each call */
|
||||
public static final ProtocolVersion[] VALUES = values();
|
||||
|
||||
//version's network id found at https://wiki.vg/Protocol_version_numbers
|
||||
private final int networkId;
|
||||
|
||||
//minor version, such as 16
|
||||
private int minorVersion;
|
||||
|
||||
//friendly name displayed in %player-version% placeholder
|
||||
private final String friendlyName;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given network id
|
||||
* @param networkId - network id of this version
|
||||
*/
|
||||
ProtocolVersion(int networkId){
|
||||
this.networkId = networkId;
|
||||
this.minorVersion = Integer.parseInt(toString().split("_")[1]);
|
||||
this.friendlyName = toString().substring(1).replace("_", ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given friendly name
|
||||
* @param friendlyName - friendly name to display
|
||||
*/
|
||||
ProtocolVersion(String friendlyName){
|
||||
this.networkId = 999;
|
||||
this.minorVersion = 18;
|
||||
this.friendlyName = friendlyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version's network id
|
||||
* @return version's network id
|
||||
*/
|
||||
public int getNetworkId() {
|
||||
return networkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user-friendly name of the version (such as 1.16.2 instead of V1_16_2)
|
||||
* @return user-friendly name of the version
|
||||
*/
|
||||
public String getFriendlyName() {
|
||||
return friendlyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns minor version of this release, such as 8 for 1.8.x or 13 for 1.13.x
|
||||
* @return version's minor version
|
||||
*/
|
||||
public int getMinorVersion() {
|
||||
return minorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides minor version of the version instance. This is used internally to
|
||||
* set minor version of UNKNOWN_SERVER_VERSION value to value from package to fix compatibility
|
||||
* with server forks that set bukkit version field value to "Unknown".
|
||||
* @param minorVersion
|
||||
* minor version to change value to
|
||||
*/
|
||||
public void setMinorVersion(int minorVersion) {
|
||||
this.minorVersion = minorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns enum constant of entered version or UNKNOWN_SERVER_VERSION if unknown version
|
||||
* @param friendlyName - friendly name of the version
|
||||
* @return version or UNKNOWN_SERVER_VERSION if version is unknown
|
||||
*/
|
||||
public static ProtocolVersion fromFriendlyName(String friendlyName) {
|
||||
if (friendlyName.startsWith("1.8")) return V1_8;
|
||||
try {
|
||||
return valueOf("V" + friendlyName.replace(".", "_"));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return UNKNOWN_SERVER_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns version from given network id
|
||||
* @param networkId - network id of protocol version
|
||||
* @return version from given network id
|
||||
*/
|
||||
public static ProtocolVersion fromNetworkId(int networkId) {
|
||||
for (ProtocolVersion v : VALUES) {
|
||||
if (networkId == v.getNetworkId()) return v;
|
||||
}
|
||||
return UNKNOWN_CLIENT_VERSION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import me.neznamy.tab.api.bossbar.BossBarManager;
|
||||
import me.neznamy.tab.api.config.ConfigurationFile;
|
||||
import me.neznamy.tab.api.event.EventBus;
|
||||
import me.neznamy.tab.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
|
||||
import me.neznamy.tab.api.task.ThreadManager;
|
||||
import me.neznamy.tab.api.team.TeamManager;
|
||||
|
||||
/**
|
||||
* The primary API class to get instances of other API classes
|
||||
*/
|
||||
public abstract class TabAPI {
|
||||
|
||||
private static TabAPI instance;
|
||||
|
||||
public static TabAPI getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void setInstance(TabAPI instance) {
|
||||
TabAPI.instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player object from given UUID
|
||||
* @return player object from given UUID
|
||||
* @param id - Player UUID
|
||||
*/
|
||||
public abstract TabPlayer getPlayer(UUID id);
|
||||
|
||||
/**
|
||||
* Returns player object from given name
|
||||
* @return player object from given name
|
||||
* @param name - Player name
|
||||
*/
|
||||
public abstract TabPlayer getPlayer(String name);
|
||||
|
||||
/**
|
||||
* Returns collection of all online players. Will return empty list if plugin is disabled (due to a broken configuration file for example).
|
||||
* @return collection of online players
|
||||
*/
|
||||
public abstract TabPlayer[] getOnlinePlayers();
|
||||
|
||||
/**
|
||||
* Return BossBar manager instance if the feature is enabled. Returns null otherwise.
|
||||
* @return BossBar manager
|
||||
*/
|
||||
public abstract BossBarManager getBossBarManager();
|
||||
|
||||
/**
|
||||
* Returns scoreboard manager instance if the feature is enabled. Returns null otherwise.
|
||||
* @return scoreboard manager
|
||||
*/
|
||||
public abstract ScoreboardManager getScoreboardManager();
|
||||
|
||||
/**
|
||||
* Returns team manager instance if the feature is enabled, false otherwise
|
||||
* @return team manager
|
||||
*/
|
||||
public abstract TeamManager getTeamManager();
|
||||
|
||||
public abstract HeaderFooterManager getHeaderFooterManager();
|
||||
|
||||
/**
|
||||
* Returns PlaceholderManager instance
|
||||
* @return PlaceholderManager instance
|
||||
*/
|
||||
public abstract PlaceholderManager getPlaceholderManager();
|
||||
|
||||
/**
|
||||
* Returns feature manager instance
|
||||
* @return feature manager instance
|
||||
*/
|
||||
public abstract FeatureManager getFeatureManager();
|
||||
|
||||
public abstract TablistFormatManager getTablistFormatManager();
|
||||
|
||||
/**
|
||||
* Gets the event bus for registering listeners for TAB events.
|
||||
*
|
||||
* @return the event bus
|
||||
*/
|
||||
public abstract EventBus getEventBus();
|
||||
|
||||
/**
|
||||
* Returns server version. On proxy installation returns PROXY.
|
||||
* @return server version
|
||||
*/
|
||||
public abstract ProtocolVersion getServerVersion();
|
||||
|
||||
/**
|
||||
* Prints message into console
|
||||
* @param message - message to print
|
||||
* @param translateColors - true if colors should be translated, false if not
|
||||
*/
|
||||
public abstract void sendConsoleMessage(String message, boolean translateColors);
|
||||
|
||||
public abstract ThreadManager getThreadManager();
|
||||
|
||||
public abstract ConfigurationFile getPlayerCache();
|
||||
|
||||
public abstract ConfigurationFile getConfig();
|
||||
|
||||
public abstract PropertyConfiguration getGroups();
|
||||
|
||||
public abstract PropertyConfiguration getUsers();
|
||||
|
||||
public abstract void debug(String message);
|
||||
|
||||
public abstract void logError(String message, Throwable t);
|
||||
|
||||
public abstract void setBrokenFile(String file);
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
|
||||
|
||||
public abstract class TabFeature {
|
||||
|
||||
private final String featureName;
|
||||
private final String refreshDisplayName;
|
||||
protected final String[] disabledServers;
|
||||
private final boolean serverWhitelistMode;
|
||||
protected final String[] disabledWorlds;
|
||||
private final boolean worldWhitelistMode;
|
||||
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
private final List<String> methodOverrides = new ArrayList<>();
|
||||
|
||||
protected TabFeature(String featureName, String refreshDisplayName) {
|
||||
this(featureName, refreshDisplayName, null, null);
|
||||
}
|
||||
|
||||
protected TabFeature(String featureName, String refreshDisplayName, List<String> disabledServers, List<String> disabledWorlds) {
|
||||
this.featureName = featureName;
|
||||
this.refreshDisplayName = refreshDisplayName;
|
||||
if (disabledServers != null) {
|
||||
this.disabledServers = disabledServers.toArray(new String[0]);
|
||||
serverWhitelistMode = disabledServers.contains("WHITELIST");
|
||||
} else {
|
||||
this.disabledServers = new String[0];
|
||||
serverWhitelistMode = false;
|
||||
}
|
||||
if (disabledWorlds != null) {
|
||||
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
|
||||
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
|
||||
} else {
|
||||
this.disabledWorlds = new String[0];
|
||||
worldWhitelistMode = false;
|
||||
}
|
||||
try {
|
||||
if (getClass().getMethod("onCommand", TabPlayer.class, String.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onCommand");
|
||||
if (getClass().getMethod("onJoin", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onJoin");
|
||||
if (getClass().getMethod("onQuit", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onQuit");
|
||||
if (getClass().getMethod("onWorldChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onWorldChange");
|
||||
if (getClass().getMethod("onServerChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onServerChange");
|
||||
if (getClass().getMethod("onDisplayObjective", TabPlayer.class, PacketPlayOutScoreboardDisplayObjective.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onDisplayObjective");
|
||||
if (getClass().getMethod("onLoginPacket", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onLoginPacket");
|
||||
if (getClass().getMethod("onObjective", TabPlayer.class, PacketPlayOutScoreboardObjective.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onObjective");
|
||||
if (getClass().getMethod("onPlayerInfo", TabPlayer.class, PacketPlayOutPlayerInfo.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onPlayerInfo");
|
||||
if (getClass().getMethod("onPacketReceive", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onPacketReceive");
|
||||
if (getClass().getMethod("onPacketSend", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onPacketSend");
|
||||
if (getClass().getMethod("refresh", TabPlayer.class, boolean.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("refresh");
|
||||
if (getClass().getMethod("onVanishStatusChange", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onVanishStatusChange");
|
||||
} catch (NoSuchMethodException e) {
|
||||
//this will never happen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all players and sends packets
|
||||
*/
|
||||
public void load() {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads all players and sends clear packets
|
||||
*/
|
||||
public void unload() {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes command from player
|
||||
* @param sender - command sender
|
||||
* @param message - command line
|
||||
* @return true if event should be cancelled, false if not
|
||||
*/
|
||||
public boolean onCommand(TabPlayer sender, String message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes join event
|
||||
* @param connectedPlayer - player who connected
|
||||
*/
|
||||
public void onJoin(TabPlayer connectedPlayer) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes quit event
|
||||
* @param disconnectedPlayer - player who disconnected
|
||||
*/
|
||||
public void onQuit(TabPlayer disconnectedPlayer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes world switch
|
||||
* @param changed - player who switched world
|
||||
* @param from - world player changed from
|
||||
* @param to - world player changed to
|
||||
*/
|
||||
public void onWorldChange(TabPlayer changed, String from, String to) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes server switch
|
||||
* @param changed - player who switched server
|
||||
* @param from - server player changed from
|
||||
* @param to - server player changed to
|
||||
*/
|
||||
public void onServerChange(TabPlayer changed, String from, String to) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the packet send and returns true if packet should be cancelled
|
||||
* @param receiver - player receiving packet
|
||||
* @param packet - received packet
|
||||
*/
|
||||
public void onDisplayObjective(TabPlayer receiver, PacketPlayOutScoreboardDisplayObjective packet) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes login packet, only available on BungeeCord
|
||||
* @param packetReceiver - player receiving client reset packet
|
||||
*/
|
||||
public void onLoginPacket(TabPlayer packetReceiver) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the packet send
|
||||
* @param receiver - player receiving packet
|
||||
* @param packet - received packet
|
||||
*/
|
||||
public void onObjective(TabPlayer receiver, PacketPlayOutScoreboardObjective packet) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the packet send and possibly modifies it
|
||||
* @param receiver - player receiving packet
|
||||
* @param info - received packet
|
||||
*/
|
||||
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes raw packet sent by client
|
||||
* @param sender - packet sender
|
||||
* @param packet - packet received
|
||||
* @return true if false should be cancelled, false if not
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes raw packet sent to client
|
||||
* @param receiver - packet receiver
|
||||
* @param packet - the packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs refresh of specified player
|
||||
* @param refreshed - player to refresh
|
||||
* @param force - if refresh should be forced despite refresh seemingly not needed
|
||||
*/
|
||||
public void refresh(TabPlayer refreshed, boolean force) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
public void onVanishStatusChange(TabPlayer player){
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this feature as one using specified placeholders
|
||||
* @param placeholders - placeholders to add as used in this feature
|
||||
*/
|
||||
public void addUsedPlaceholders(Collection<String> placeholders) {
|
||||
if (placeholders.isEmpty()) return;
|
||||
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if world or server is disabled, false if not
|
||||
* @param world - world to check
|
||||
* @param server - server to check
|
||||
* @return true if feature should be disabled, false if not
|
||||
*/
|
||||
public boolean isDisabled(String server, String world) {
|
||||
boolean contains = contains(disabledWorlds, world);
|
||||
if (worldWhitelistMode) contains = !contains;
|
||||
if (contains) return true;
|
||||
contains = contains(disabledServers, server);
|
||||
if (serverWhitelistMode) contains = !contains;
|
||||
return contains;
|
||||
}
|
||||
|
||||
protected boolean contains(String[] list, String element) {
|
||||
if (element == null) return false;
|
||||
for (String s : list) {
|
||||
if (s.endsWith("*")) {
|
||||
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
|
||||
} else {
|
||||
if (element.equalsIgnoreCase(s)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of the feature displayed in /tab cpu.
|
||||
* @return name of the feature displayed in /tab cpu
|
||||
*/
|
||||
public String getFeatureName() {
|
||||
return featureName;
|
||||
}
|
||||
|
||||
public boolean overridesMethod(String method) {
|
||||
return methodOverrides.contains(method);
|
||||
}
|
||||
|
||||
public boolean isDisabledPlayer(TabPlayer p) {
|
||||
return disabledPlayers.contains(p);
|
||||
}
|
||||
|
||||
public void addDisabledPlayer(TabPlayer p) {
|
||||
if (disabledPlayers.contains(p)) return;
|
||||
disabledPlayers.add(p);
|
||||
}
|
||||
|
||||
public void removeDisabledPlayer(TabPlayer p) {
|
||||
disabledPlayers.remove(p);
|
||||
}
|
||||
|
||||
public String getRefreshDisplayName() {
|
||||
return refreshDisplayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.protocol.Skin;
|
||||
import me.neznamy.tab.api.protocol.TabPacket;
|
||||
|
||||
/**
|
||||
* An interface representing a player
|
||||
*/
|
||||
public interface TabPlayer {
|
||||
|
||||
/**
|
||||
* Refreshes all visuals on the player
|
||||
*/
|
||||
void forceRefresh();
|
||||
|
||||
/**
|
||||
* Returns player's name
|
||||
* @return Player's name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns player's UUID
|
||||
* @return Player's UUID
|
||||
*/
|
||||
UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Returns player's uuid used in TabList. This may only be different from real uuid if
|
||||
* TAB is installed on velocity with some specific velocity setup
|
||||
* @return player's uuid in TabList
|
||||
*/
|
||||
UUID getTablistUUID();
|
||||
|
||||
/**
|
||||
* Returns player's protocol version
|
||||
* @return player's protocol version
|
||||
*/
|
||||
ProtocolVersion getVersion();
|
||||
|
||||
/**
|
||||
* Returns platform-specific entity
|
||||
* @return an instance of bukkit/bungee player depending on platform
|
||||
*/
|
||||
Object getPlayer();
|
||||
|
||||
/**
|
||||
* Returns player's current world name (on BungeeCord this requires bridge installed)
|
||||
* @return name of world where player is currently in, "N/A" on BungeeCord if bridge is not installed
|
||||
*/
|
||||
String getWorld();
|
||||
|
||||
/**
|
||||
* Returns player's current server name on BungeeCord (on bukkit this returns "N/A")
|
||||
* @return name of server where player is currently in, "N/A" on bukkit
|
||||
*/
|
||||
String getServer();
|
||||
|
||||
/**
|
||||
* Performs platform-specific API call to check for permission and returns the result
|
||||
* @param permission - the permission to check for
|
||||
* @return - true if player has permission, false if not
|
||||
*/
|
||||
boolean hasPermission(String permission);
|
||||
|
||||
/**
|
||||
* Sends the player a custom universal packet
|
||||
* @param packet - packet to send
|
||||
*/
|
||||
void sendCustomPacket(TabPacket packet);
|
||||
|
||||
/**
|
||||
* Sends the player a custom universal packet and adds that packet into counter that
|
||||
* is displayed in /tab cpu
|
||||
* @param packet - packet to send
|
||||
* @param feature - feature to increment sent packet counter of
|
||||
*/
|
||||
void sendCustomPacket(TabPacket packet, TabFeature feature);
|
||||
|
||||
/**
|
||||
* Sends the player a custom universal packet and adds that packet into counter that
|
||||
* is displayed in /tab cpu
|
||||
* @param packet - packet to send
|
||||
* @param feature - feature to increment sent packet counter of
|
||||
*/
|
||||
void sendCustomPacket(TabPacket packet, String feature);
|
||||
|
||||
/**
|
||||
* Sends the player a platform-specific packet
|
||||
* @param packet - an instance of packet depending on platform
|
||||
*/
|
||||
void sendPacket(Object packet);
|
||||
|
||||
/**
|
||||
* Sends the player a platform-specific packet and adds that packet into counter that
|
||||
* is displayed in /tab cpu
|
||||
* @param packet - an instance of packet depending on platform
|
||||
* @param feature - feature to increment sent packet counter of
|
||||
*/
|
||||
void sendPacket(Object packet, TabFeature feature);
|
||||
|
||||
/**
|
||||
* Sends the player a platform-specific packet and adds that packet into counter that
|
||||
* is displayed in /tab cpu
|
||||
* @param packet - an instance of packet depending on platform
|
||||
* @param feature - feature to increment sent packet counter of
|
||||
*/
|
||||
void sendPacket(Object packet, String feature);
|
||||
|
||||
/**
|
||||
* Returns player's property by name
|
||||
* @param name - name of property
|
||||
* @return the property or null if not found
|
||||
*/
|
||||
Property getProperty(String name);
|
||||
|
||||
/**
|
||||
* Sends a message to the player
|
||||
* @param message - message to be sent
|
||||
* @param translateColors - whether colors should be translated or not
|
||||
*/
|
||||
void sendMessage(String message, boolean translateColors);
|
||||
|
||||
/**
|
||||
* Sends specified component as a chat message
|
||||
* @param message - message to send
|
||||
*/
|
||||
void sendMessage(IChatBaseComponent message);
|
||||
|
||||
/**
|
||||
* Returns player's primary permission group
|
||||
* @return player's group depending on configuration
|
||||
*/
|
||||
String getGroup();
|
||||
|
||||
/**
|
||||
* Returns player's channel or null if server is 1.7 or older
|
||||
* @return player's channel
|
||||
*/
|
||||
Channel getChannel();
|
||||
|
||||
/**
|
||||
* Returns player's ping calculated by server
|
||||
* @return player's ping
|
||||
*/
|
||||
int getPing();
|
||||
|
||||
/**
|
||||
* Returns player's skin data
|
||||
* @return player's skin
|
||||
*/
|
||||
Skin getSkin();
|
||||
|
||||
/**
|
||||
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
|
||||
* @return true if player is fully loaded, false otherwise
|
||||
*/
|
||||
boolean isLoaded();
|
||||
|
||||
/**
|
||||
* Sets property with specified name to new value. If property did not exist before, it is
|
||||
* created and true is returned. If it existed, it is overridden and true is returned. False is returned otherwise.
|
||||
* @param feature - feature using this property to get placeholders registered
|
||||
* @param identifier - property name
|
||||
* @param rawValue - new raw value
|
||||
* @return true if value changed / did not exist, false if value did not change
|
||||
*/
|
||||
boolean setProperty(TabFeature feature, String identifier, String rawValue);
|
||||
|
||||
/**
|
||||
* Loads property from config using standard property loading algorithm
|
||||
* @param property - property name to load
|
||||
* @return true if value did not exist or changed, false otherwise
|
||||
*/
|
||||
boolean loadPropertyFromConfig(TabFeature feature, String property);
|
||||
|
||||
/**
|
||||
* Loads property from config using standard property loading algorithm. If the property is
|
||||
* not set in config, sets it to ifNotSet value
|
||||
* @param property - property name to load
|
||||
* @param ifNotSet - value to use if property is not defined in config
|
||||
* @return true if value did not exist or changed, false otherwise
|
||||
*/
|
||||
boolean loadPropertyFromConfig(TabFeature feature, String property, String ifNotSet);
|
||||
|
||||
/**
|
||||
* Returns name of player's scoreboard team or null if NameTag feature is disabled
|
||||
* @return name of player's team
|
||||
*/
|
||||
String getTeamName();
|
||||
|
||||
/**
|
||||
* Returns user-friendly explanation of team name
|
||||
* @return explanation behind team name
|
||||
*/
|
||||
String getTeamNameNote();
|
||||
|
||||
/**
|
||||
* Returns true if player is disguised using LibsDisguises
|
||||
* @return true if player is disguised, false if not
|
||||
*/
|
||||
boolean isDisguised();
|
||||
|
||||
/**
|
||||
* Returns true if player has invisibility potion, false if not. For bukkit, API is used, for BungeeCord
|
||||
* bukkit bridge is used
|
||||
* @return true if player has invisibility potion, false if not
|
||||
*/
|
||||
boolean hasInvisibilityPotion();
|
||||
|
||||
/**
|
||||
* Currently only PV hook on BungeeCord for global PlayerList function. Looking to expand this
|
||||
* in the future
|
||||
* @return true if vanished with PV on BungeeCord
|
||||
*/
|
||||
boolean isVanished();
|
||||
|
||||
/**
|
||||
* Returns true if player is online according to server
|
||||
* @return true if online, false if not
|
||||
*/
|
||||
boolean isOnline();
|
||||
|
||||
/**
|
||||
* Returns GameMode of the player (0 for survival, 1 creative, 2 adventure, 3 spectator)
|
||||
* @return GameMode of the player
|
||||
*/
|
||||
int getGamemode();
|
||||
|
||||
boolean isBedrockPlayer();
|
||||
|
||||
void setTemporaryGroup(String group);
|
||||
|
||||
boolean hasTemporaryGroup();
|
||||
|
||||
void resetTemporaryGroup();
|
||||
|
||||
String getNickname();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
public interface TablistFormatManager {
|
||||
|
||||
void setPrefix(TabPlayer player, String prefix);
|
||||
|
||||
void setName(TabPlayer player, String customName);
|
||||
|
||||
void setSuffix(TabPlayer player, String suffix);
|
||||
|
||||
void resetPrefix(TabPlayer player);
|
||||
|
||||
void resetName(TabPlayer player);
|
||||
|
||||
void resetSuffix(TabPlayer player);
|
||||
|
||||
String getCustomPrefix(TabPlayer player);
|
||||
|
||||
String getCustomName(TabPlayer player);
|
||||
|
||||
String getCustomSuffix(TabPlayer player);
|
||||
|
||||
String getOriginalPrefix(TabPlayer player);
|
||||
|
||||
String getOriginalName(TabPlayer player);
|
||||
|
||||
String getOriginalSuffix(TabPlayer player);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
/**
|
||||
* An enum representing all available BossBar colors.
|
||||
* Calling ordinal() will return color's network ID.
|
||||
*/
|
||||
public enum BarColor {
|
||||
|
||||
PINK,
|
||||
BLUE,
|
||||
RED,
|
||||
GREEN,
|
||||
YELLOW,
|
||||
PURPLE,
|
||||
WHITE
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
/**
|
||||
* An enum representing all available BossBar styles.
|
||||
* Calling ordinal() will return style's network ID.
|
||||
* Style names are equal to NMS names.
|
||||
*/
|
||||
public enum BarStyle {
|
||||
|
||||
PROGRESS("SOLID"),
|
||||
NOTCHED_6("SEGMENTED_6"),
|
||||
NOTCHED_10("SEGMENTED_10"),
|
||||
NOTCHED_12("SEGMENTED_12"),
|
||||
NOTCHED_20("SEGMENTED_20");
|
||||
|
||||
/** Style's name used in Bukkit API */
|
||||
private final String bukkitName;
|
||||
|
||||
/**
|
||||
* Initializes enum constant with given bukkit name
|
||||
*
|
||||
* @param bukkitName
|
||||
* Name of color to be returned in {@code getBukkitName()}
|
||||
*/
|
||||
BarStyle(String bukkitName){
|
||||
this.bukkitName = bukkitName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of this style in Bukkit API
|
||||
* @return name of this style in Bukkit API
|
||||
*/
|
||||
public String getBukkitName() {
|
||||
return bukkitName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* An interface representing a BossBar line.
|
||||
* <p>
|
||||
* For clients 1.8 and lower it uses wither, which only allows 1 bar
|
||||
* to be displayed at a time. Entity packets are not available on BungeeCord,
|
||||
* where nothing will be displayed for these players.
|
||||
* <p>
|
||||
* For 1.9+ it uses the new BossBar feature, allowing configurable styles
|
||||
* and colors, as well as display several bars at once. Limit of maximum
|
||||
* displayed BossBars depends on client resolution and GUI scale, up to
|
||||
* 9 concurrent BossBars displayed at once.
|
||||
*/
|
||||
public interface BossBar {
|
||||
|
||||
/**
|
||||
* Returns name of this BossBar. If it was defined in config, returns
|
||||
* the name specified in config. If it was made using the API, returns
|
||||
* randomly generated ID given to this BossBar when creating.
|
||||
* @return name of BossBar
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns randomly generated uuid of this BossBar used internally to match
|
||||
* the BossBar in client with packets or with Bukkit API BossBar instances.
|
||||
* @return uuid of this BossBar
|
||||
*/
|
||||
UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Changes BossBar title to specified string, supporting placeholders.
|
||||
* <p>
|
||||
* If title contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified title is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param title
|
||||
* New title to display in this BossBar
|
||||
*/
|
||||
void setTitle(String title);
|
||||
|
||||
/**
|
||||
* Changes BossBar progress to specified string, supporting placeholders.
|
||||
* The string must be a string version of a float value, or a placeholder that
|
||||
* outputs a float value with range 0-100.
|
||||
* <p>
|
||||
* If progress contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified progress is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param progress
|
||||
* New progress to use in this BossBar
|
||||
*/
|
||||
void setProgress(String progress);
|
||||
|
||||
/**
|
||||
* Changes BossBar progress to specified value ranging from 0 to 100.
|
||||
* <p>
|
||||
* If specified progress is equal to the current one, nothing happens.
|
||||
* @param progress
|
||||
* New progress to use in this BossBar
|
||||
*/
|
||||
void setProgress(float progress);
|
||||
|
||||
/**
|
||||
* Changes BossBar color to specified string, supporting placeholders.
|
||||
* The string must be a string version of one of the supported values,
|
||||
* or a placeholder that outputs one of them.
|
||||
* <p>
|
||||
* If color contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified color is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param color
|
||||
* New color to use in this BossBar
|
||||
*/
|
||||
void setColor(String color);
|
||||
|
||||
/**
|
||||
* Changes BossBar color to specified enum constant.
|
||||
* <p>
|
||||
* If specified color is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param color
|
||||
* New color to use in this BossBar
|
||||
*/
|
||||
void setColor(BarColor color);
|
||||
|
||||
/**
|
||||
* Changes BossBar style to specified string, supporting placeholders.
|
||||
* The string must be a string version of one of the supported values,
|
||||
* or a placeholder that outputs one of them.
|
||||
* <p>
|
||||
* If style contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified style is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param style
|
||||
* New style to use in this BossBar
|
||||
*/
|
||||
void setStyle(String style);
|
||||
|
||||
/**
|
||||
* Changes BossBar style to specified enum constant.
|
||||
* <p>
|
||||
* If specified style is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param style
|
||||
* New style to use in this BossBar
|
||||
*/
|
||||
void setStyle(BarStyle style);
|
||||
|
||||
/**
|
||||
* Returns current title of the BossBar in raw format. If it contains placeholders,
|
||||
* their raw identifiers are used in the result.
|
||||
* @return title of the BossBar
|
||||
*/
|
||||
String getTitle();
|
||||
|
||||
/**
|
||||
* Returns progress of the BossBar as a string, which is either entered string
|
||||
* containing placeholders or entered number converted to string
|
||||
* @return entered progress as a string
|
||||
*/
|
||||
String getProgress();
|
||||
|
||||
/**
|
||||
* Returns color of the BossBar as a string, which is either entered string
|
||||
* containing placeholders or entered enum value converted to string
|
||||
* @return entered color as a string
|
||||
*/
|
||||
String getColor();
|
||||
|
||||
/**
|
||||
* Returns style of the BossBar as a string, which is either entered string
|
||||
* containing placeholders or entered enum value converted to string
|
||||
* @return entered style as a string
|
||||
*/
|
||||
String getStyle();
|
||||
|
||||
/**
|
||||
* Registers this BossBar to specified player.
|
||||
* <p>
|
||||
* If the player already sees this BossBar, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* Player to register this BossBar to
|
||||
*/
|
||||
void addPlayer(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Unregisters this BossBar from specified player.
|
||||
* <p>
|
||||
* If the player does not see this BossBar, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* Player to unregister this BossBar from
|
||||
*/
|
||||
void removePlayer(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns list of players who can see this BossBar.
|
||||
* <p>
|
||||
* The returned list can only be used to read. Writing
|
||||
* will not work properly. For adding/removing players see {@link #addPlayer(TabPlayer)}
|
||||
* and {@link #removePlayer(TabPlayer)}.
|
||||
* @return List of players seeing this BossBar
|
||||
*/
|
||||
List<TabPlayer> getPlayers();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if specified player can see this BossBar, {@code false} if not
|
||||
* @param player
|
||||
* Player to check for
|
||||
* @return {@code true} if contains, {@code false} if not
|
||||
*/
|
||||
boolean containsPlayer(TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* An interface allowing work with BossBars such as creating, sending
|
||||
* and toggling.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link me.neznamy.tab.api.TabAPI#getBossBarManager()}.
|
||||
* This requires the BossBar feature to be enabled in config, otherwise the method will
|
||||
* return null.
|
||||
*/
|
||||
public interface BossBarManager {
|
||||
|
||||
/**
|
||||
* Creates BossBar with specified parameters and registers it to BossBar manager,
|
||||
* allowing to retrieve it later.
|
||||
*
|
||||
* @param title
|
||||
* Title to display, supports placeholders
|
||||
*
|
||||
* @param progress
|
||||
* Progress to use. Must be between 0 and 100
|
||||
*
|
||||
* @param color
|
||||
* Color to use
|
||||
*
|
||||
* @param style
|
||||
* Style to use
|
||||
* @return The newly created BossBar
|
||||
*/
|
||||
BossBar createBossBar(String title, float progress, BarColor color, BarStyle style);
|
||||
|
||||
/**
|
||||
* Creates BossBar with specified parameters and registers it to BossBar manager,
|
||||
* allowing to retrieve it later.
|
||||
*
|
||||
* @param title
|
||||
* Title to display, supports placeholders
|
||||
*
|
||||
* @param progress
|
||||
* Progress to use. Must be a string version of a number, or
|
||||
* a placeholder that returns a number
|
||||
*
|
||||
* @param color
|
||||
* Color to use. Must be a string version of one of enum constants, or
|
||||
* a placeholder that returns one of them
|
||||
*
|
||||
* @param style
|
||||
* Style to use. Must be a string version of one of enum constants, or
|
||||
* a placeholder that returns one of them
|
||||
* @return The newly created BossBar
|
||||
*/
|
||||
BossBar createBossBar(String title, String progress, String color, String style);
|
||||
|
||||
/**
|
||||
* Returns registered BossBar with given name. For BossBars from config it is the name
|
||||
* they were created with, for API BossBars their randomly generated name, which can be
|
||||
* retrieved using {@link me.neznamy.tab.api.bossbar.BossBar#getName()}.
|
||||
* Returns null if no such BossBar exists.
|
||||
*
|
||||
* @param name
|
||||
* Name of registered BossBar
|
||||
* @return BossBar with specified name
|
||||
*/
|
||||
BossBar getBossBar(String name);
|
||||
|
||||
/**
|
||||
* Returns registered BossBar with specified UUID. This UUID is randomly generated when
|
||||
* BossBar is created and used in packets / to match which the one from Bukkit API.
|
||||
* Returns null if no such BossBar exists.
|
||||
*
|
||||
* @param id
|
||||
* UUID to return BossBar by
|
||||
* @return BossBar with specified uuid
|
||||
*/
|
||||
BossBar getBossBar(UUID id);
|
||||
|
||||
/**
|
||||
* Returns a map of all registered BossBars. This includes both BossBars from config
|
||||
* and those registered via API.
|
||||
* <p>
|
||||
* Map key is BossBar's name available in {@link me.neznamy.tab.api.bossbar.BossBar#getName()},
|
||||
* map value is the BossBar interface.
|
||||
* @return all registered BossBars
|
||||
*/
|
||||
Map<String, BossBar> getRegisteredBossBars();
|
||||
|
||||
/**
|
||||
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
|
||||
*
|
||||
* @param player
|
||||
* Player to toggle BossBar for
|
||||
*
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if toggle message should be sent, {@code false} if not
|
||||
* @see #hasBossBarVisible(TabPlayer)
|
||||
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
void toggleBossBar(TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns true if player can see BossBars, false if toggled via command or API.
|
||||
*
|
||||
* @param player
|
||||
* Player to check toggle status of
|
||||
* @return {@code true} if visible, {@code false} if disabled (toggled)
|
||||
* @see #toggleBossBar(TabPlayer, boolean)
|
||||
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
boolean hasBossBarVisible(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sets BossBar visibility of player to set value. If value did not change, nothing happens.
|
||||
* If visibility changed, toggle message is sent if {@code sendToggleMessage} is {@code true}.
|
||||
*
|
||||
* @param player
|
||||
* Player to set BossBar visibility of
|
||||
* @param visible
|
||||
* {@code true} if BossBar should be visible, {@code false} if not
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if toggle message should be sent if value changed, {@code false} if not
|
||||
* @see #toggleBossBar(TabPlayer, boolean)
|
||||
* @see #hasBossBarVisible(TabPlayer)
|
||||
*/
|
||||
void setBossBarVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Temporarily displays registered BossBar to player for specified amount of time in milliseconds.
|
||||
* @param player
|
||||
* Player to show BossBar to
|
||||
* @param bossBar
|
||||
* Name of registered BossBar to show
|
||||
* @param duration
|
||||
* In milliseconds for how long should BossBar be displayed
|
||||
* @throws IllegalArgumentException
|
||||
* if no BossBar with specified name exists
|
||||
* @see #announceBossBar(String, int)
|
||||
* @see #getAnnouncedBossBars()
|
||||
*/
|
||||
void sendBossBarTemporarily(TabPlayer player, String bossBar, int duration);
|
||||
|
||||
/**
|
||||
* Temporarily displays registered BossBar to all players for specified amount of time in milliseconds.
|
||||
* @param bossBar
|
||||
* Name of registered BossBar to show
|
||||
* @param duration
|
||||
* In milliseconds for how long should BossBar be displayed
|
||||
* @throws IllegalArgumentException
|
||||
* if no BossBar with specified name exists
|
||||
* @see #sendBossBarTemporarily(TabPlayer, String, int)
|
||||
* @see #getAnnouncedBossBars()
|
||||
*/
|
||||
void announceBossBar(String bossBar, int duration);
|
||||
|
||||
/**
|
||||
* Returns list of BossBars which are currently being announced.
|
||||
* @return list of currently active BossBar announcements
|
||||
* @see #sendBossBarTemporarily(TabPlayer, String, int)
|
||||
* @see #announceBossBar(String, int)
|
||||
*/
|
||||
List<BossBar> getAnnouncedBossBars();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* Class for click event action in chat component
|
||||
*/
|
||||
public class ChatClickable {
|
||||
|
||||
/** Click action */
|
||||
private final EnumClickAction action;
|
||||
|
||||
/** Click value */
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given action and value.
|
||||
* @param action
|
||||
* click event action
|
||||
* @param value
|
||||
* click event value
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code action} is null or {@code value} is null
|
||||
*/
|
||||
public ChatClickable(EnumClickAction action, String value) {
|
||||
Preconditions.checkNotNull(action, "hover action");
|
||||
Preconditions.checkNotNull(value, "hover value");
|
||||
this.action = action;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns click action defined in constructor
|
||||
* @return click action
|
||||
*/
|
||||
public EnumClickAction getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns click action defined in constructor
|
||||
* @return click action
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum for all possible click actions
|
||||
*/
|
||||
public enum EnumClickAction {
|
||||
|
||||
OPEN_URL,
|
||||
RUN_COMMAND,
|
||||
CHANGE_PAGE, //since 1.8
|
||||
SUGGEST_COMMAND,
|
||||
COPY_TO_CLIPBOARD //since 1.15
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
public class ChatComponentEntity extends IChatBaseComponent {
|
||||
|
||||
private final String type;
|
||||
private final UUID id;
|
||||
private final String name;
|
||||
|
||||
public ChatComponentEntity(String type, UUID id, String name) {
|
||||
Preconditions.checkNotNull(type, "type");
|
||||
Preconditions.checkNotNull(id, "id");
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toRawText() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{\"type\":\"%s\",\"id\":\"%s\",\"name\":{\"text\":\"%s\"}}", type, id, name);
|
||||
}
|
||||
|
||||
public JsonObject toJson() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("type", type);
|
||||
json.addProperty("id", id.toString());
|
||||
JsonObject json2 = new JsonObject();
|
||||
json2.addProperty("text", name);
|
||||
json.add("name", json2);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* Class for hover event action in chat component
|
||||
*/
|
||||
public class ChatHoverable {
|
||||
|
||||
/** Hover action */
|
||||
private final EnumHoverAction action;
|
||||
|
||||
/** Hover value */
|
||||
private final IChatBaseComponent value;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given action and value.
|
||||
* @param action
|
||||
* hover event action
|
||||
* @param value
|
||||
* hover event value
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code action} is null or {@code value} is null
|
||||
*/
|
||||
public ChatHoverable(EnumHoverAction action, IChatBaseComponent value) {
|
||||
Preconditions.checkNotNull(action, "hover action");
|
||||
Preconditions.checkNotNull(value, "hover value");
|
||||
this.action = action;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hover action defined in constructor
|
||||
* @return hover action
|
||||
*/
|
||||
public EnumHoverAction getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hover value defined in constructor
|
||||
* @return hover value
|
||||
*/
|
||||
public IChatBaseComponent getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum for all possible hover actions
|
||||
*/
|
||||
public enum EnumHoverAction {
|
||||
|
||||
SHOW_TEXT,
|
||||
SHOW_ITEM,
|
||||
SHOW_ENTITY
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.chat.ChatClickable.EnumClickAction;
|
||||
import me.neznamy.tab.api.chat.ChatHoverable.EnumHoverAction;
|
||||
|
||||
public class ChatModifier {
|
||||
|
||||
private TextColor color;
|
||||
|
||||
private Boolean bold;
|
||||
private Boolean italic;
|
||||
private Boolean underlined;
|
||||
private Boolean strikethrough;
|
||||
private Boolean obfuscated;
|
||||
|
||||
private ChatClickable clickEvent;
|
||||
private ChatHoverable hoverEvent;
|
||||
|
||||
private String font;
|
||||
|
||||
private ProtocolVersion targetVersion;
|
||||
|
||||
public ChatModifier() {
|
||||
}
|
||||
|
||||
public ChatModifier(ChatModifier modifier) {
|
||||
Preconditions.checkNotNull(modifier, "modifier");
|
||||
this.color = modifier.color == null ? null : new TextColor(modifier.color);
|
||||
this.bold = modifier.bold;
|
||||
this.italic = modifier.italic;
|
||||
this.underlined = modifier.underlined;
|
||||
this.strikethrough = modifier.strikethrough;
|
||||
this.obfuscated = modifier.obfuscated;
|
||||
this.clickEvent = modifier.clickEvent == null ? null : new ChatClickable(modifier.clickEvent.getAction(), modifier.clickEvent.getValue());
|
||||
this.hoverEvent = modifier.hoverEvent == null ? null : new ChatHoverable(modifier.hoverEvent.getAction(), modifier.hoverEvent.getValue());
|
||||
this.font = modifier.font;
|
||||
this.targetVersion = modifier.targetVersion;
|
||||
}
|
||||
|
||||
public TextColor getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(TextColor color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public Boolean getBold() {
|
||||
return bold;
|
||||
}
|
||||
|
||||
public void setBold(Boolean bold) {
|
||||
this.bold = bold;
|
||||
}
|
||||
|
||||
public Boolean getItalic() {
|
||||
return italic;
|
||||
}
|
||||
|
||||
public void setItalic(Boolean italic) {
|
||||
this.italic = italic;
|
||||
}
|
||||
|
||||
public Boolean getUnderlined() {
|
||||
return underlined;
|
||||
}
|
||||
|
||||
public void setUnderlined(Boolean underlined) {
|
||||
this.underlined = underlined;
|
||||
}
|
||||
|
||||
public Boolean getStrikethrough() {
|
||||
return strikethrough;
|
||||
}
|
||||
|
||||
public void setStrikethrough(Boolean strikethrough) {
|
||||
this.strikethrough = strikethrough;
|
||||
}
|
||||
|
||||
public Boolean getObfuscated() {
|
||||
return obfuscated;
|
||||
}
|
||||
|
||||
public void setObfuscated(Boolean obfuscated) {
|
||||
this.obfuscated = obfuscated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if bold is defined and set to true, false otherwise
|
||||
* @return true if bold is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isBold(){
|
||||
return Boolean.TRUE.equals(bold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if italic is defined and set to true, false otherwise
|
||||
* @return true if italic is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isItalic(){
|
||||
return Boolean.TRUE.equals(italic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if underlined is defined and set to true, false otherwise
|
||||
* @return true if underlined is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isUnderlined(){
|
||||
return Boolean.TRUE.equals(underlined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if strikethrough is defined and set to true, false otherwise
|
||||
* @return true if strikethrough is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isStrikethrough(){
|
||||
return Boolean.TRUE.equals(strikethrough);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if obfuscation is defined and set to true, false otherwise
|
||||
* @return true if obfuscation is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isObfuscated(){
|
||||
return Boolean.TRUE.equals(obfuscated);
|
||||
}
|
||||
|
||||
public ChatClickable getClickEvent() {
|
||||
return clickEvent;
|
||||
}
|
||||
|
||||
public void setClickEvent(ChatClickable clickEvent) {
|
||||
this.clickEvent = clickEvent;
|
||||
}
|
||||
|
||||
public ChatHoverable getHoverEvent() {
|
||||
return hoverEvent;
|
||||
}
|
||||
|
||||
public void setHoverEvent(ChatHoverable hoverEvent) {
|
||||
this.hoverEvent = hoverEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click action to OPEN_URL and url to given value
|
||||
* @param url - url to open
|
||||
*/
|
||||
public void onClickOpenUrl(String url) {
|
||||
Preconditions.checkNotNull(url, "url");
|
||||
clickEvent = new ChatClickable(EnumClickAction.OPEN_URL, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click action to RUN_COMMAND and command to given value
|
||||
* @param command - command to perform, might be without / to send a chat message
|
||||
*/
|
||||
public void onClickRunCommand(String command) {
|
||||
Preconditions.checkNotNull(command, "command");
|
||||
clickEvent = new ChatClickable(EnumClickAction.RUN_COMMAND, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click action to SUGGEST_COMMAND and command to given value
|
||||
* @param command - command to suggest
|
||||
*/
|
||||
public void onClickSuggestCommand(String command) {
|
||||
Preconditions.checkNotNull(command, "command");
|
||||
clickEvent = new ChatClickable(EnumClickAction.SUGGEST_COMMAND, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click action to CHANGE_PAGE and page id to given value
|
||||
* @param newPage - id of new page
|
||||
*/
|
||||
public void onClickChangePage(int newPage) {
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() < 8) throw new UnsupportedOperationException("change_page click action is not supported on <1.8");
|
||||
clickEvent = new ChatClickable(EnumClickAction.CHANGE_PAGE, String.valueOf(newPage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click action to COPY_TO_CLIPBOARD and text to provided value
|
||||
* @param text - text to copy to clipboard on click
|
||||
*/
|
||||
public void onClickCopyToClipBoard(String text) {
|
||||
Preconditions.checkNotNull(text, "text");
|
||||
clickEvent = new ChatClickable(EnumClickAction.COPY_TO_CLIPBOARD, text);
|
||||
}
|
||||
|
||||
public void onClick(EnumClickAction action, String value) {
|
||||
Preconditions.checkNotNull(action, "action");
|
||||
Preconditions.checkNotNull(value, "value");
|
||||
clickEvent = new ChatClickable(action, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets hover action to SHOW_TEXT and text to given value
|
||||
* @param text - text to show
|
||||
*/
|
||||
public void onHoverShowText(IChatBaseComponent text) {
|
||||
Preconditions.checkNotNull(text, "text");
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_TEXT, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets hover action to SHOW_ITEM and item to given value
|
||||
* @param serializedItem - item to show
|
||||
*/
|
||||
public void onHoverShowItem(String serializedItem) {
|
||||
Preconditions.checkNotNull(serializedItem, "item");
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ITEM, new IChatBaseComponent(serializedItem));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets hover action to SHOW_ENTITY and entity data to given values
|
||||
* @param type - entity type
|
||||
* @param id - entity uuid
|
||||
* @param name - entity custom name
|
||||
*/
|
||||
public void onHoverShowEntity(String type, UUID id, String name) {
|
||||
Preconditions.checkNotNull(type, "type");
|
||||
Preconditions.checkNotNull(id, "id");
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
|
||||
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:\"%s\",id:\"%s\",name:\"{\\\"text\\\":\\\"%s\\\"}\"}", type, id, name)));
|
||||
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() == 12) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:\"%s\",id:\"%s\",name:\"%s\"}", type, id, name)));
|
||||
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:%s,id:%s,name:%s}", type, id, name)));
|
||||
} else {
|
||||
throw new IllegalStateException("show_entity hover action is not supported on <1.8");
|
||||
}
|
||||
}
|
||||
|
||||
public void onHover(EnumHoverAction action, IChatBaseComponent value) {
|
||||
Preconditions.checkNotNull(action, "action");
|
||||
Preconditions.checkNotNull(value, "value");
|
||||
hoverEvent = new ChatHoverable(action, value);
|
||||
}
|
||||
|
||||
public String getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public void setFont(String font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public JSONObject serialize() {
|
||||
JSONObject json = new JSONObject();
|
||||
if (color != null) json.put("color", targetVersion.getMinorVersion() >= 16 ? color.toString() : color.getLegacyColor().toString().toLowerCase());
|
||||
if (bold != null) json.put("bold", bold);
|
||||
if (italic != null) json.put("italic", italic);
|
||||
if (underlined != null) json.put("underlined", underlined);
|
||||
if (strikethrough != null) json.put("strikethrough", strikethrough);
|
||||
if (obfuscated != null) json.put("obfuscated", obfuscated);
|
||||
if (clickEvent != null) {
|
||||
JSONObject click = new JSONObject();
|
||||
click.put("action", clickEvent.getAction().toString().toLowerCase());
|
||||
click.put("value", clickEvent.getValue());
|
||||
json.put("clickEvent", click);
|
||||
}
|
||||
if (hoverEvent != null) {
|
||||
JSONObject hover = new JSONObject();
|
||||
hover.put("action", hoverEvent.getAction().toString().toLowerCase());
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
|
||||
hover.put("contents", hoverEvent.getValue());
|
||||
} else {
|
||||
hover.put("value", TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 ?
|
||||
hoverEvent.getValue() : hoverEvent.getValue().toRawText());
|
||||
}
|
||||
json.put("hoverEvent", hover);
|
||||
}
|
||||
if (font != null) json.put("font", font);
|
||||
return json;
|
||||
}
|
||||
|
||||
public String getMagicCodes() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (isBold()) builder.append(EnumChatFormat.BOLD.getFormat());
|
||||
if (isItalic()) builder.append(EnumChatFormat.ITALIC.getFormat());
|
||||
if (isUnderlined()) builder.append(EnumChatFormat.UNDERLINE.getFormat());
|
||||
if (isStrikethrough()) builder.append(EnumChatFormat.STRIKETHROUGH.getFormat());
|
||||
if (isObfuscated()) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public void setTargetVersion(ProtocolVersion targetVersion) {
|
||||
this.targetVersion = targetVersion;
|
||||
}
|
||||
|
||||
public ProtocolVersion getTargetVersion() {
|
||||
return targetVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class representing a chat component that was serialized before,
|
||||
* but deserialize was requested in PacketPlayOutPlayerInfo packet.
|
||||
* This is used to read and modify the packet and then rewrite it.
|
||||
* However, display name components must be deserialized to properly
|
||||
* forward them in the new packet, but its value is not actually
|
||||
* used anywhere.
|
||||
* <p>
|
||||
* To avoid this heavy and completely unnecessary operation, the original
|
||||
* string is saved and returned during serialization. If any read/write
|
||||
* operation is performed, the string is fully deserialized and this object
|
||||
* will act like a normal chat component.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class DeserializedChatComponent extends IChatBaseComponent {
|
||||
|
||||
/** The original serialized component string */
|
||||
private final String json;
|
||||
|
||||
/**
|
||||
* Flag tracking whether this component was fully deserialized
|
||||
* in case of a read operation to avoid repeating the deserialization
|
||||
* process on every request.
|
||||
*/
|
||||
private boolean deserialized;
|
||||
|
||||
/**
|
||||
* Flag tracking whether this component was modified, which means
|
||||
* a new string has to be created instead of returning the input one.
|
||||
*/
|
||||
private boolean modified;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
*
|
||||
* @param json
|
||||
* serialized component as string
|
||||
*/
|
||||
public DeserializedChatComponent(String json) {
|
||||
Preconditions.checkNotNull(json, "json");
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (modified) return super.toString();
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(ProtocolVersion clientVersion) {
|
||||
if (modified) return super.toString(clientVersion);
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IChatBaseComponent> getExtra(){
|
||||
if (!deserialized) deserialize();
|
||||
return super.getExtra();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
if (!deserialized) deserialize();
|
||||
return super.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModifier getModifier() {
|
||||
if (!deserialized) deserialize();
|
||||
return super.getModifier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components){
|
||||
if (!deserialized) deserialize();
|
||||
modified = true;
|
||||
return super.setExtra(components);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtra(IChatBaseComponent child) {
|
||||
if (!deserialized) deserialize();
|
||||
modified = true;
|
||||
super.addExtra(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModifier(ChatModifier modifier) {
|
||||
if (!deserialized) deserialize();
|
||||
modified = true;
|
||||
super.setModifier(modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a full deserialization process on this component.
|
||||
*/
|
||||
void deserialize() {
|
||||
deserialized = true;
|
||||
if (json.startsWith("\"") && json.endsWith("\"") && json.length() > 1) {
|
||||
//simple component with only text used, minecraft serializer outputs the text in quotes instead of full json
|
||||
setText(json.substring(1, json.length()-1));
|
||||
return;
|
||||
}
|
||||
JSONObject jsonObject;
|
||||
try {
|
||||
jsonObject = (JSONObject) new JSONParser().parse(json);
|
||||
} catch (ParseException e) {
|
||||
TabAPI.getInstance().logError("Failed to deserialize json component " + json, e);
|
||||
return;
|
||||
}
|
||||
/* if (jsonObject.containsKey("type")) {
|
||||
return new ChatComponentEntity((String) jsonObject.get("type"), UUID.fromString((String) jsonObject.get("id")), IChatBaseComponent.deserialize(jsonObject.get("name").toString()).toFlatText());
|
||||
}*/
|
||||
setText((String) jsonObject.get("text"));
|
||||
getModifier().setBold(getBoolean(jsonObject, "bold"));
|
||||
getModifier().setItalic(getBoolean(jsonObject, "italic"));
|
||||
getModifier().setUnderlined(getBoolean(jsonObject, "underlined"));
|
||||
getModifier().setStrikethrough(getBoolean(jsonObject, "strikethrough"));
|
||||
getModifier().setObfuscated(getBoolean(jsonObject, "obfuscated"));
|
||||
getModifier().setColor(TextColor.fromString(((String) jsonObject.get("color"))));
|
||||
if (jsonObject.containsKey("clickEvent")) {
|
||||
JSONObject clickEvent = (JSONObject) jsonObject.get("clickEvent");
|
||||
String action = (String) clickEvent.get("action");
|
||||
String value = clickEvent.get("value").toString();
|
||||
getModifier().onClick(ChatClickable.EnumClickAction.valueOf(action.toUpperCase()), value);
|
||||
}
|
||||
if (jsonObject.containsKey("hoverEvent")) {
|
||||
JSONObject hoverEvent = (JSONObject) jsonObject.get("hoverEvent");
|
||||
String action = (String) hoverEvent.get("action");
|
||||
String value = (String) hoverEvent.get("value");
|
||||
getModifier().onHover(ChatHoverable.EnumHoverAction.valueOf(action.toUpperCase()), deserialize(value));
|
||||
}
|
||||
if (jsonObject.containsKey("extra")) {
|
||||
List<Object> list = (List<Object>) jsonObject.get("extra");
|
||||
for (Object extra : list) {
|
||||
String string = extra.toString();
|
||||
//reverting .toString() removing "" for simple text
|
||||
if (!string.startsWith("{")) string = "\"" + string + "\"";
|
||||
addExtra(IChatBaseComponent.deserialize(string));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns boolean value of requested key from map
|
||||
*
|
||||
* @param jsonObject
|
||||
* map to get value from
|
||||
* @param key
|
||||
* name of key
|
||||
* @return value from json object or null if not present
|
||||
*/
|
||||
private static Boolean getBoolean(JSONObject jsonObject, String key) {
|
||||
Preconditions.checkNotNull(jsonObject, "json object");
|
||||
Preconditions.checkNotNull(key, "key");
|
||||
String value = String.valueOf(jsonObject.getOrDefault(key, null));
|
||||
return "null".equals(value) ? null : Boolean.parseBoolean(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import me.neznamy.tab.api.chat.rgb.RGBUtils;
|
||||
|
||||
/**
|
||||
* An enum containing all possible legacy color codes and magic codes. Also contains handy color-related methods.
|
||||
*/
|
||||
public enum EnumChatFormat {
|
||||
|
||||
BLACK('0', "000000"),
|
||||
DARK_BLUE('1', "0000AA"),
|
||||
DARK_GREEN('2', "00AA00"),
|
||||
DARK_AQUA('3', "00AAAA"),
|
||||
DARK_RED('4', "AA0000"),
|
||||
DARK_PURPLE('5', "AA00AA"),
|
||||
GOLD('6', "FFAA00"),
|
||||
GRAY('7', "AAAAAA"),
|
||||
DARK_GRAY('8', "555555"),
|
||||
BLUE('9', "5555FF"),
|
||||
GREEN('a', "55FF55"),
|
||||
AQUA('b', "55FFFF"),
|
||||
RED('c', "FF5555"),
|
||||
LIGHT_PURPLE('d', "FF55FF"),
|
||||
YELLOW('e', "FFFF55"),
|
||||
WHITE('f', "FFFFFF"),
|
||||
OBFUSCATED('k'),
|
||||
BOLD('l'),
|
||||
STRIKETHROUGH('m'),
|
||||
UNDERLINE('n'),
|
||||
ITALIC('o'),
|
||||
RESET('r');
|
||||
|
||||
/** Creating a constant to avoid memory allocations on each request */
|
||||
public static final EnumChatFormat[] VALUES = values();
|
||||
|
||||
/** The symbol minecraft uses to colorize text */
|
||||
public static final char COLOR_CHAR = 0x00a7;
|
||||
|
||||
/** The color symbol in form of a string */
|
||||
public static final String COLOR_STRING = String.valueOf(COLOR_CHAR);
|
||||
|
||||
/** Character representing the color or magic code */
|
||||
private final char character;
|
||||
|
||||
/** Red value of this constant, 0 for magic codes */
|
||||
private final short red;
|
||||
|
||||
/** Green value of this constant, 0 for magic codes */
|
||||
private final short green;
|
||||
|
||||
/** Blue value of this constant, 0 for magic codes */
|
||||
private final short blue;
|
||||
|
||||
/** Color as 6-digit hex code, null for magic codes */
|
||||
private final String hexCode;
|
||||
|
||||
/** Color symbol followed by constant's character */
|
||||
private final String chatFormat;
|
||||
|
||||
/**
|
||||
* Constructs new color instance with given character and hex code
|
||||
* @param character
|
||||
* character which the color goes by
|
||||
* @param hexCode
|
||||
* 6-digit hex code of the color
|
||||
*/
|
||||
EnumChatFormat(char character, String hexCode) {
|
||||
this.character = character;
|
||||
this.chatFormat = String.valueOf(COLOR_CHAR) + character;
|
||||
this.hexCode = hexCode;
|
||||
int hexColor = Integer.parseInt(hexCode, 16);
|
||||
red = (short) ((hexColor >> 16) & 0xFF);
|
||||
green = (short) ((hexColor >> 8) & 0xFF);
|
||||
blue = (short) (hexColor & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new magic code instance with given character
|
||||
* @param character
|
||||
* character representing the magic code
|
||||
*/
|
||||
EnumChatFormat(char character) {
|
||||
this.character = character;
|
||||
this.chatFormat = String.valueOf(COLOR_CHAR) + character;
|
||||
red = 0;
|
||||
green = 0;
|
||||
blue = 0;
|
||||
hexCode = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns red value of this color code
|
||||
* @return red value
|
||||
*/
|
||||
public short getRed() {
|
||||
return red;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns green value of this color code
|
||||
* @return green value
|
||||
*/
|
||||
public short getGreen() {
|
||||
return green;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns blue value of this color code
|
||||
* @return blue value
|
||||
*/
|
||||
public short getBlue() {
|
||||
return blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns enum value based on provided character or null if character is not valid
|
||||
* @param c
|
||||
* color code character (0-9, a-f, k-o, r)
|
||||
* @return instance from the character or null if character is not valid
|
||||
*/
|
||||
public static EnumChatFormat getByChar(char c) {
|
||||
for (EnumChatFormat format : VALUES) {
|
||||
if (format.character == c) return format;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns enum value of last colors used in given string.
|
||||
* If it's null, empty or does not contain color codes, WHITE is returned.
|
||||
* @param string
|
||||
* string to check last colors of
|
||||
* @return last used color code in given string or WHITE if nothing is found
|
||||
*/
|
||||
public static EnumChatFormat lastColorsOf(String string) {
|
||||
if (string == null || string.length() == 0) return EnumChatFormat.WHITE;
|
||||
String legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
|
||||
String last = getLastColors(legacyText);
|
||||
if (last.length() > 0) {
|
||||
char c = last.toCharArray()[1];
|
||||
for (EnumChatFormat e : VALUES) {
|
||||
if (e.character == c) return e;
|
||||
}
|
||||
}
|
||||
return EnumChatFormat.WHITE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns color char followed by color's character
|
||||
* @return color char followed by color's character
|
||||
*/
|
||||
public String getFormat() {
|
||||
return chatFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns character representing this color
|
||||
* @return character representing this color
|
||||
*/
|
||||
public char getCharacter() {
|
||||
return character;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hex code of this format, null if this is a magic code
|
||||
* @return hex code of this format
|
||||
*/
|
||||
public String getHexCode() {
|
||||
return hexCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns enum value with exact red, green and blue values or null if no match was found
|
||||
* @param red
|
||||
* exact red value
|
||||
* @param green
|
||||
* exact green value
|
||||
* @param blue
|
||||
* exact blue value
|
||||
* @return enum value or null if no such combination exists
|
||||
*/
|
||||
public static EnumChatFormat fromRGBExact(int red, int green, int blue){
|
||||
for (EnumChatFormat format : VALUES) {
|
||||
if (format.red == red && format.green == green && format.blue == blue) return format;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Color translation method taken from bukkit, which converts '&' symbol into
|
||||
* the actual color character if followed by a valid color character.
|
||||
* @param textToTranslate
|
||||
* text to replace color symbol in
|
||||
* @return colorized string from provided text
|
||||
*/
|
||||
public static String color(String textToTranslate){
|
||||
if (textToTranslate == null) return null;
|
||||
if (!textToTranslate.contains("&")) return textToTranslate;
|
||||
char[] b = textToTranslate.toCharArray();
|
||||
for (int i = 0; i < b.length - 1; i++) {
|
||||
if ((b[i] == '&') && ("0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx".indexOf(b[(i + 1)]) > -1)){
|
||||
b[i] = COLOR_CHAR;
|
||||
b[(i + 1)] = Character.toLowerCase(b[(i + 1)]);
|
||||
}
|
||||
}
|
||||
return new String(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns back the color symbol into '&' symbol in provided text.
|
||||
* @param text
|
||||
* text to revert colors in
|
||||
* @return reverted text
|
||||
*/
|
||||
public static String decolor(String text) {
|
||||
if (text == null) return null;
|
||||
if (!text.contains(COLOR_STRING)) return text;
|
||||
return text.replace(COLOR_CHAR, '&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Code taken from bukkit, which returns last color codes used in provided text.
|
||||
* @param input
|
||||
* text to get last colors from
|
||||
* @return last colors used in provided text or empty string if nothing was found
|
||||
*/
|
||||
public static String getLastColors(String input) {
|
||||
if (input == null) return "";
|
||||
StringBuilder result = new StringBuilder();
|
||||
int length = input.length();
|
||||
for (int index = length - 1; index > -1; index--){
|
||||
char section = input.charAt(index);
|
||||
if ((section == COLOR_CHAR || section == '&') && (index < length - 1)){
|
||||
char c = input.charAt(index + 1);
|
||||
if ("0123456789AaBbCcDdEeFfKkLlMmNnOoRr".contains(String.valueOf(c))) {
|
||||
result.insert(0, COLOR_CHAR);
|
||||
result.insert(1, c);
|
||||
if ("0123456789AaBbCcDdEeFfRr".contains(String.valueOf(c))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.IChatBaseComponent class to make work with it much easier
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class IChatBaseComponent {
|
||||
|
||||
/**
|
||||
* Component cache maps to avoid large memory allocations as well as
|
||||
* higher CPU usage when using animations which send the same text on repeat.
|
||||
*/
|
||||
private static final Map<String, IChatBaseComponent> componentCache = new HashMap<>();
|
||||
private static final Map<IChatBaseComponent, String> serializeCacheModern = new HashMap<>();
|
||||
private static final Map<IChatBaseComponent, String> serializeCacheLegacy = new HashMap<>();
|
||||
|
||||
/** Text of the component */
|
||||
private String text;
|
||||
|
||||
/** Chat modifier containing color, magic codes, hover and click event */
|
||||
private ChatModifier modifier = new ChatModifier();
|
||||
|
||||
/** Extra components used in "extra" field */
|
||||
private List<IChatBaseComponent> extra;
|
||||
|
||||
/**
|
||||
* Constructs a new empty component
|
||||
*/
|
||||
public IChatBaseComponent() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new component which is a clone of provided component
|
||||
* @param component
|
||||
* component to clone
|
||||
*/
|
||||
public IChatBaseComponent(IChatBaseComponent component) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
this.text = component.text;
|
||||
this.modifier = new ChatModifier(component.modifier);
|
||||
for (IChatBaseComponent child : component.getExtra()) {
|
||||
addExtra(new IChatBaseComponent(child));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given text
|
||||
* @param text
|
||||
* text to display
|
||||
*/
|
||||
public IChatBaseComponent(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of extra components. If no extra components are defined, returns empty list.
|
||||
* @return list of extra components
|
||||
*/
|
||||
public List<IChatBaseComponent> getExtra(){
|
||||
if (extra == null) return Collections.emptyList();
|
||||
return extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets full list of extra components to given list. Does not allow empty list.
|
||||
* @param components
|
||||
* components to use as extra
|
||||
* @return self
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code components} is an empty list
|
||||
*/
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components){
|
||||
if (components.isEmpty()) throw new IllegalArgumentException("Unexpected empty array of components"); //exception taken from minecraft
|
||||
this.extra = components;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends provided component as extra component
|
||||
* @param child
|
||||
* component to append
|
||||
*/
|
||||
public void addExtra(IChatBaseComponent child) {
|
||||
Preconditions.checkNotNull(child, "extra");
|
||||
if (extra == null) extra = new ArrayList<>();
|
||||
extra.add(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns text of this component
|
||||
* @return text of this component
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns chat modifier of this component
|
||||
* @return chat modifier of this component
|
||||
*/
|
||||
public ChatModifier getModifier() {
|
||||
return modifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets modifier to provided value
|
||||
* @param modifier
|
||||
* modifier to set value to
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code modifier} is null
|
||||
*/
|
||||
public void setModifier(ChatModifier modifier) {
|
||||
Preconditions.checkNotNull(modifier, "modifier");
|
||||
this.modifier = modifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes text of this component
|
||||
* @param text
|
||||
* text to show
|
||||
*/
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes string and returns created component. If provided string is null, returns null.
|
||||
* @param json
|
||||
* serialized string
|
||||
* @return Deserialized component or null if input is null
|
||||
*/
|
||||
public static IChatBaseComponent deserialize(String json) {
|
||||
if (json == null) return null;
|
||||
return new DeserializedChatComponent(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the component to a string representing the serialized component.
|
||||
* This method is only used internally by json library since it's missing
|
||||
* protocol version field used by the method.
|
||||
* @return serialized component in string form
|
||||
* @see #toString(ProtocolVersion)
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
JSONObject json = new JSONObject();
|
||||
if (text != null) json.put("text", text);
|
||||
if (modifier.getTargetVersion() == null) modifier.setTargetVersion(TabAPI.getInstance().getServerVersion()); //packet.toString() was called as a part of a debug message
|
||||
json.putAll(modifier.serialize());
|
||||
if (extra != null) json.put("extra", extra);
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this component with colors based on client version.
|
||||
* If client version is <1.16, HEX colors will be converted to legacy colors.
|
||||
* @param clientVersion
|
||||
* client version to adapt component for
|
||||
* @return serialized string
|
||||
*/
|
||||
public String toString(ProtocolVersion clientVersion) {
|
||||
return toString(clientVersion, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this component with colors based on client version.
|
||||
* If client version is <1.16, HEX colors will be converted to legacy colors.
|
||||
* @param clientVersion
|
||||
* client version to adapt component for
|
||||
* @param sendTranslatableIfEmpty
|
||||
* whether empty translatable should be sent if text is empty or not
|
||||
* @return serialized string
|
||||
*/
|
||||
public String toString(ProtocolVersion clientVersion, boolean sendTranslatableIfEmpty) {
|
||||
if (extra == null) {
|
||||
if (text == null) return null;
|
||||
if (text.length() == 0) {
|
||||
if (sendTranslatableIfEmpty) {
|
||||
return "{\"translate\":\"\"}";
|
||||
} else {
|
||||
return "{\"text\":\"\"}";
|
||||
}
|
||||
}
|
||||
}
|
||||
modifier.setTargetVersion(clientVersion);
|
||||
for (IChatBaseComponent child : getExtra()) {
|
||||
child.modifier.setTargetVersion(clientVersion);
|
||||
}
|
||||
String string;
|
||||
if (clientVersion.getMinorVersion() >= 16) {
|
||||
if (serializeCacheModern.containsKey(this)) return serializeCacheModern.get(this);
|
||||
string = toString();
|
||||
if (serializeCacheModern.size() > 10000) serializeCacheModern.clear();
|
||||
serializeCacheModern.put(this, string);
|
||||
} else {
|
||||
if (serializeCacheLegacy.containsKey(this)) return serializeCacheLegacy.get(this);
|
||||
string = toString();
|
||||
if (serializeCacheLegacy.size() > 10000) serializeCacheLegacy.clear();
|
||||
serializeCacheLegacy.put(this, string);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns organized component from colored text
|
||||
* @param originalText
|
||||
* text to convert
|
||||
* @return organized component from colored text
|
||||
*/
|
||||
public static IChatBaseComponent fromColoredText(String originalText){
|
||||
Preconditions.checkNotNull(originalText, "text");
|
||||
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText), false);
|
||||
List<IChatBaseComponent> components = new ArrayList<>();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
IChatBaseComponent component = new IChatBaseComponent();
|
||||
for (int i = 0; i < text.length(); i++){
|
||||
char c = text.charAt(i);
|
||||
if (c == EnumChatFormat.COLOR_CHAR){
|
||||
i++;
|
||||
if (i >= text.length()) {
|
||||
break;
|
||||
}
|
||||
c = text.charAt(i);
|
||||
if ((c >= 'A') && (c <= 'Z')) {
|
||||
c = (char)(c + ' ');
|
||||
}
|
||||
EnumChatFormat format = EnumChatFormat.getByChar(c);
|
||||
if (format != null){
|
||||
if (builder.length() > 0) {
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
component = new IChatBaseComponent(component);
|
||||
component.text = null;
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
switch (format){
|
||||
case BOLD:
|
||||
component.modifier.setBold(true);
|
||||
break;
|
||||
case ITALIC:
|
||||
component.modifier.setItalic(true);
|
||||
break;
|
||||
case UNDERLINE:
|
||||
component.modifier.setUnderlined(true);
|
||||
break;
|
||||
case STRIKETHROUGH:
|
||||
component.modifier.setStrikethrough(true);
|
||||
break;
|
||||
case OBFUSCATED:
|
||||
component.modifier.setObfuscated(true);
|
||||
break;
|
||||
case RESET:
|
||||
component = new IChatBaseComponent();
|
||||
component.modifier.setColor(new TextColor(EnumChatFormat.WHITE));
|
||||
break;
|
||||
default:
|
||||
component = new IChatBaseComponent();
|
||||
component.modifier.setColor(new TextColor(format));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (c == '#' && text.length() > i+6){
|
||||
String hex = text.substring(i+1, i+7);
|
||||
if (RGBUtils.getInstance().isHexCode(hex)) {
|
||||
TextColor color;
|
||||
if (containsLegacyCode(text, i)) {
|
||||
color = new TextColor(hex, EnumChatFormat.getByChar(text.charAt(i+8)));
|
||||
i += 8;
|
||||
} else {
|
||||
color = new TextColor(hex);
|
||||
i += 6;
|
||||
}
|
||||
if (builder.length() > 0){
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
component = new IChatBaseComponent();
|
||||
component.modifier.setColor(color);
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
return new IChatBaseComponent("").setExtra(components);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if text contains legacy color request at defined RGB index start
|
||||
* @param text
|
||||
* text to check
|
||||
* @param i
|
||||
* current index start
|
||||
* @return true if legacy color is defined, false if not
|
||||
*/
|
||||
private static boolean containsLegacyCode(String text, int i) {
|
||||
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
|
||||
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this component into a simple text with legacy colors (the closest match if color is set to RGB)
|
||||
* @return The simple text format
|
||||
*/
|
||||
public String toLegacyText() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
append(builder, "");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends text to string builder, might also add color and magic codes if they are different
|
||||
* from previous component in chain.
|
||||
* @param builder
|
||||
* builder to append text to
|
||||
* @param previousFormatting
|
||||
* colors and magic codes in previous component
|
||||
* @return wew formatting, might be identical to previous one
|
||||
*/
|
||||
private String append(StringBuilder builder, String previousFormatting) {
|
||||
String formatting = previousFormatting;
|
||||
if (text != null) {
|
||||
formatting = getFormatting();
|
||||
if (!formatting.equals(previousFormatting)) {
|
||||
builder.append(formatting);
|
||||
}
|
||||
builder.append(text);
|
||||
|
||||
}
|
||||
for (IChatBaseComponent component : getExtra()) {
|
||||
formatting = component.append(builder, formatting);
|
||||
}
|
||||
return formatting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns colors and magic codes of this component
|
||||
* @return used colors and magic codes
|
||||
*/
|
||||
private String getFormatting() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (modifier.getColor() != null) {
|
||||
if (modifier.getColor().getLegacyColor() == EnumChatFormat.WHITE) {
|
||||
//preventing unwanted &r -> &f conversion and stopping the <1.13 client bug fix from working
|
||||
builder.append(EnumChatFormat.RESET.getFormat());
|
||||
} else {
|
||||
builder.append(modifier.getColor().getLegacyColor().getFormat());
|
||||
}
|
||||
}
|
||||
builder.append(modifier.getMagicCodes());
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns raw text without colors, only works correctly when component is organized
|
||||
* @return raw text in this component and all child components
|
||||
*/
|
||||
public String toRawText() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (text != null) builder.append(text);
|
||||
for (IChatBaseComponent child : getExtra()) {
|
||||
if (child.text != null) builder.append(child.text);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the component into flat text with used colors (including rgb) and magic codes
|
||||
* @return converted text
|
||||
*/
|
||||
public String toFlatText() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (modifier.getColor() != null) builder.append("#").append(modifier.getColor().getHexCode());
|
||||
builder.append(modifier.getMagicCodes());
|
||||
if (text != null) builder.append(text);
|
||||
for (IChatBaseComponent child : getExtra()) {
|
||||
builder.append(child.toFlatText());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most optimized component based on text. Returns null if text is null,
|
||||
* organized component if RGB colors are used or simple component with only text field
|
||||
* containing the whole text when no RGB colors are used
|
||||
* @param text
|
||||
* text to create component from
|
||||
* @return The most performance-optimized component based on text
|
||||
*/
|
||||
public static IChatBaseComponent optimizedComponent(String text){
|
||||
if (text == null) return null;
|
||||
if (componentCache.containsKey(text)) return componentCache.get(text);
|
||||
IChatBaseComponent component;
|
||||
if (text.contains("#") || text.contains("&x") || text.contains(EnumChatFormat.COLOR_CHAR + "x")){
|
||||
//contains RGB colors
|
||||
component = IChatBaseComponent.fromColoredText(text);
|
||||
} else {
|
||||
//no RGB
|
||||
component = new IChatBaseComponent(text);
|
||||
}
|
||||
if (componentCache.size() > 10000) componentCache.clear();
|
||||
componentCache.put(text, component);
|
||||
return component;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing a component color, which can be either RGB or legacy code.
|
||||
*/
|
||||
public class TextColor {
|
||||
|
||||
/**
|
||||
* RGB values as a single number of 3 8-bit numbers (0-255).
|
||||
* It is only initialized if colors are actually used to avoid
|
||||
* unnecessary memory allocations with string operations.
|
||||
*/
|
||||
private int rgb = -1;
|
||||
|
||||
/** Closest legacy color to this color object. */
|
||||
private EnumChatFormat legacyColor;
|
||||
|
||||
/** HEX code of this color as a 6-digit hexadecimal string without "#" prefix. */
|
||||
private String hexCode;
|
||||
|
||||
/**
|
||||
* Boolean value whether the legacy color was forced with constructor or should be
|
||||
* automatically assigned as closest color.
|
||||
* This value is used in gradients when converting text for legacy players. */
|
||||
private boolean legacyColorForced;
|
||||
|
||||
/**
|
||||
* Constructs new instance as a clone of the provided color.
|
||||
*
|
||||
* @param color
|
||||
* color to create a clone of
|
||||
* @throws IllegalArgumentException
|
||||
* if color is {@code null}
|
||||
*/
|
||||
public TextColor(TextColor color) {
|
||||
Preconditions.checkNotNull(color, "color");
|
||||
rgb = color.rgb;
|
||||
legacyColor = color.legacyColor;
|
||||
hexCode = color.hexCode;
|
||||
legacyColorForced = color.legacyColorForced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance from provided 6-digit hex code string
|
||||
*
|
||||
* @param hexCode
|
||||
* a 6-digit combination of hex numbers as a string
|
||||
* @throws IllegalArgumentException
|
||||
* if hexCode is {@code null}
|
||||
*/
|
||||
public TextColor(String hexCode) {
|
||||
Preconditions.checkNotNull(hexCode, "hex code");
|
||||
this.hexCode = hexCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance from provided 6-digit hex code and forced legacy color
|
||||
*
|
||||
* @param hexCode
|
||||
* 6-digit combination of hex numbers as a string
|
||||
* @param legacyColor
|
||||
* color to use for legacy clients instead of using the closest legacy color
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code hexCode} is {@code null} or {@code legacyColor} is {@code null}
|
||||
*/
|
||||
public TextColor(String hexCode, EnumChatFormat legacyColor) {
|
||||
Preconditions.checkNotNull(hexCode, "hex code");
|
||||
Preconditions.checkNotNull(legacyColor, "legacy color");
|
||||
this.hexCode = hexCode;
|
||||
this.legacyColorForced = true;
|
||||
this.legacyColor = legacyColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance from provided legacy color
|
||||
*
|
||||
* @param legacyColor
|
||||
* legacy color to construct the instance from
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code legacyColor} is {@code null}
|
||||
*/
|
||||
public TextColor(EnumChatFormat legacyColor) {
|
||||
Preconditions.checkNotNull(legacyColor, "legacy color");
|
||||
this.rgb = (legacyColor.getRed() << 16) + (legacyColor.getGreen() << 8) + legacyColor.getBlue();
|
||||
this.hexCode = legacyColor.getHexCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with red, green and blue values
|
||||
*
|
||||
* @param red
|
||||
* red value
|
||||
* @param green
|
||||
* green value
|
||||
* @param blue
|
||||
* blue value
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code red}, {@code green} or {@code blue} is out of range ({@code 0-255})
|
||||
*/
|
||||
public TextColor(int red, int green, int blue) {
|
||||
Preconditions.checkRange(red, 0, 255, "red");
|
||||
Preconditions.checkRange(green, 0, 255, "green");
|
||||
Preconditions.checkRange(blue, 0, 255, "blue");
|
||||
this.rgb = (red << 16) + (green << 8) + blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the closest legacy color based currently provided values
|
||||
*/
|
||||
private EnumChatFormat loadClosestColor() {
|
||||
double minMaxDist = 9999;
|
||||
double maxDist;
|
||||
EnumChatFormat closestColor = EnumChatFormat.WHITE;
|
||||
for (EnumChatFormat color : EnumChatFormat.VALUES) {
|
||||
int rDiff = Math.abs(color.getRed() - getRed());
|
||||
int gDiff = Math.abs(color.getGreen() - getGreen());
|
||||
int bDiff = Math.abs(color.getBlue() - getBlue());
|
||||
maxDist = rDiff;
|
||||
if (gDiff > maxDist) maxDist = gDiff;
|
||||
if (bDiff > maxDist) maxDist = bDiff;
|
||||
if (maxDist < minMaxDist) {
|
||||
minMaxDist = maxDist;
|
||||
closestColor = color;
|
||||
}
|
||||
}
|
||||
return closestColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code red} value
|
||||
* @return red value
|
||||
*/
|
||||
public int getRed() {
|
||||
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
|
||||
return (rgb >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code green} value
|
||||
* @return green value
|
||||
*/
|
||||
public int getGreen() {
|
||||
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
|
||||
return (rgb >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code blue} value
|
||||
* @return blue value
|
||||
*/
|
||||
public int getBlue() {
|
||||
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
|
||||
return rgb & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the closest legacy color of this color object.
|
||||
* If the color was defined in constructor, it's returned.
|
||||
* Otherwise, the closest color is calculated the then returned.
|
||||
* @return closest legacy color
|
||||
*/
|
||||
public EnumChatFormat getLegacyColor() {
|
||||
if (legacyColor == null) legacyColor = loadClosestColor();
|
||||
return legacyColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rgb combination as a 6-digit hex code string
|
||||
* @return the rgb combination as a 6-digit hex code string
|
||||
*/
|
||||
public String getHexCode() {
|
||||
if (hexCode == null) hexCode = String.format("%06X", rgb);
|
||||
return hexCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the color into a valid color value used in color field in chat component.
|
||||
* That is either 6-digit hex code prefixed with '#', or lowercase legacy color.
|
||||
* @return the color serialized for use in chat component
|
||||
*/
|
||||
public String toString() {
|
||||
EnumChatFormat legacyEquivalent = EnumChatFormat.fromRGBExact(getRed(), getGreen(), getBlue());
|
||||
if (legacyEquivalent != null) {
|
||||
//not sending old colors as RGB to 1.16 clients if not needed as <1.16 servers will fail to apply color
|
||||
return legacyEquivalent.toString().toLowerCase();
|
||||
}
|
||||
return "#" + getHexCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if legacy color was forced with a constructor, false if not
|
||||
* @return true if forced, false if not
|
||||
*/
|
||||
public boolean isLegacyColorForced() {
|
||||
return legacyColorForced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the string as it appears in chat component and turns it into the color object.
|
||||
* If the entered string is null, returns null.
|
||||
* If it's prefixed with '#', it's considered as a hex code.
|
||||
* Otherwise, it is considered being a lowercase legacy color.
|
||||
* @param string
|
||||
* string from color field in chat component
|
||||
* @return An instance from specified string or null if string is null
|
||||
*/
|
||||
public static TextColor fromString(String string) {
|
||||
if (string == null) return null;
|
||||
if (string.startsWith("#")) return new TextColor(string.substring(1));
|
||||
return new TextColor(EnumChatFormat.valueOf(string.toUpperCase()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package me.neznamy.tab.api.chat.rgb;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.TextColor;
|
||||
import me.neznamy.tab.api.chat.rgb.format.BukkitFormat;
|
||||
import me.neznamy.tab.api.chat.rgb.format.CMIFormat;
|
||||
import me.neznamy.tab.api.chat.rgb.format.HtmlFormat;
|
||||
import me.neznamy.tab.api.chat.rgb.format.RGBFormatter;
|
||||
import me.neznamy.tab.api.chat.rgb.format.UnnamedFormat1;
|
||||
import me.neznamy.tab.api.chat.rgb.gradient.CMIGradient;
|
||||
import me.neznamy.tab.api.chat.rgb.gradient.CommonGradient;
|
||||
import me.neznamy.tab.api.chat.rgb.gradient.GradientPattern;
|
||||
import me.neznamy.tab.api.chat.rgb.gradient.KyoriGradient;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A helper class to reformat all RGB formats into the default #RRGGBB and apply gradients
|
||||
*/
|
||||
public class RGBUtils {
|
||||
|
||||
/** Instance of the class */
|
||||
private static final RGBUtils instance = new RGBUtils();
|
||||
|
||||
/** Registered RGB formatters */
|
||||
private final RGBFormatter[] formats;
|
||||
|
||||
/** Registered gradient patterns */
|
||||
private final GradientPattern[] gradients;
|
||||
|
||||
/** TAB's RGB pattern, used to convert text to bukkit format for boss bar */
|
||||
private final Pattern tabPattern = Pattern.compile("#[0-9a-fA-F]{6}");
|
||||
|
||||
/** RGB pattern for legacy codes */
|
||||
private final Pattern tabPatternLegacy = Pattern.compile("#[0-9a-fA-F]{6}\\|.");
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all RGB patterns and gradients
|
||||
*/
|
||||
public RGBUtils() {
|
||||
formats = new RGBFormatter[] {
|
||||
new BukkitFormat(),
|
||||
new CMIFormat(),
|
||||
new UnnamedFormat1(),
|
||||
new HtmlFormat()
|
||||
};
|
||||
gradients = new GradientPattern[] {
|
||||
//{#RRGGBB>}text{#RRGGBB<}
|
||||
new CMIGradient(),
|
||||
//<#RRGGBB>Text</#RRGGBB>
|
||||
new CommonGradient(Pattern.compile("<#[0-9a-fA-F]{6}>[^<]*</#[0-9a-fA-F]{6}>"),
|
||||
Pattern.compile("<#[0-9a-fA-F]{6}\\|.>[^<]*</#[0-9a-fA-F]{6}>"),
|
||||
"<#", 9, 2, 9, 7),
|
||||
//<$#RRGGBB>Text<$#RRGGBB>
|
||||
new CommonGradient(Pattern.compile("<\\$#[0-9a-fA-F]{6}>[^<]*<\\$#[0-9a-fA-F]{6}>"),
|
||||
Pattern.compile("<\\$#[0-9a-fA-F]{6}\\|.>[^<]*<\\$#[0-9a-fA-F]{6}>"),
|
||||
"<$", 10, 3, 10, 7),
|
||||
new KyoriGradient()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns instance of this class
|
||||
* @return instance
|
||||
*/
|
||||
public static RGBUtils getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all RGB formats and gradients to text and returns it.
|
||||
* This method is called on every property initialization to convert formats
|
||||
* once instead of on every refresh, if possible. Because of that, parameter
|
||||
* {@code ignorePlaceholders} exists to not break raw placeholder identifiers
|
||||
* @param text
|
||||
* original text
|
||||
* @param ignorePlaceholders
|
||||
* whether placeholders should be ignored or not
|
||||
* @return text where everything is converted to #RRGGBB
|
||||
*/
|
||||
public String applyFormats(String text, boolean ignorePlaceholders) {
|
||||
Preconditions.checkNotNull(text, "text");
|
||||
String replaced = text;
|
||||
for (GradientPattern pattern : gradients) {
|
||||
replaced = pattern.applyPattern(replaced, ignorePlaceholders);
|
||||
}
|
||||
for (RGBFormatter formatter : formats) {
|
||||
replaced = formatter.reformat(replaced);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TAB's RGB format (#RRGGBB) into bukkit one
|
||||
* (&x&r&r&g&g&b&b) for modern
|
||||
* clients (1.16+), for legacy clients it will use the closest color.
|
||||
* @param text
|
||||
* text to convert
|
||||
* @param rgbClient
|
||||
* whether client accepts RGB or not
|
||||
* @return converted text
|
||||
*/
|
||||
public String convertToBukkitFormat(String text, boolean rgbClient) {
|
||||
if (text == null) return null;
|
||||
if (!text.contains("#")) return text; //no rgb codes
|
||||
if (rgbClient) {
|
||||
//converting random formats to TAB one
|
||||
String replaced = applyFormats(text, false);
|
||||
for (Pattern p : new Pattern[]{tabPatternLegacy, tabPattern}) {
|
||||
Matcher m = p.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String hexCode = m.group();
|
||||
String fixed = "&x&" + hexCode.charAt(1) + "&" + hexCode.charAt(2) + "&" + hexCode.charAt(3) + "&" + hexCode.charAt(4) + "&" + hexCode.charAt(5) + "&" + hexCode.charAt(6);
|
||||
replaced = replaced.replace(hexCode, EnumChatFormat.color(fixed));
|
||||
}
|
||||
}
|
||||
return replaced;
|
||||
} else {
|
||||
return convertRGBtoLegacy(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all hex codes in given string to legacy codes
|
||||
* @param text
|
||||
* text to convert
|
||||
* @return translated text
|
||||
*/
|
||||
public String convertRGBtoLegacy(String text) {
|
||||
if (text == null) return null;
|
||||
if (!text.contains("#")) return EnumChatFormat.color(text);
|
||||
String applied = applyFormats(text, false);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < applied.length(); i++){
|
||||
char c = applied.charAt(i);
|
||||
if (c == '#' && applied.length() > i+6) {
|
||||
String hexCode = applied.substring(i+1, i+7);
|
||||
if (isHexCode(hexCode)) {
|
||||
if (containsLegacyCode(applied, i)) {
|
||||
sb.append(new TextColor(hexCode, EnumChatFormat.getByChar(applied.charAt(i+8))).getLegacyColor().getFormat());
|
||||
i += 8;
|
||||
} else {
|
||||
sb.append(new TextColor(hexCode).getLegacyColor().getFormat());
|
||||
i += 6;
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if entered string is a valid 6-digit combination of
|
||||
* hexadecimal numbers, false if not
|
||||
* @param string
|
||||
* string to check
|
||||
* @return {@code true} if valid, {@code false} if not
|
||||
*/
|
||||
public boolean isHexCode(String string) {
|
||||
Preconditions.checkNotNull(string, "string");
|
||||
if (string.length() != 6) return false;
|
||||
for (int i=0; i<6; i++) {
|
||||
char c = string.charAt(i);
|
||||
if (c < 48 || (c > 57 && c < 65) || (c > 70 && c < 97) || c > 102) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if text contains legacy color request at defined RGB index start
|
||||
* @param text
|
||||
* text to check
|
||||
* @param i
|
||||
* current index start
|
||||
* @return {@code true} if legacy color is defined and valid, {@code false} otherwise
|
||||
*/
|
||||
private static boolean containsLegacyCode(String text, int i) {
|
||||
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
|
||||
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package me.neznamy.tab.api.chat.rgb.format;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
|
||||
/**
|
||||
* Formatter for &x&R&R&G&G&B&B
|
||||
*/
|
||||
public class BukkitFormat implements RGBFormatter {
|
||||
|
||||
private final Pattern pattern = Pattern.compile("[" + EnumChatFormat.COLOR_CHAR + "&]x[" + EnumChatFormat.COLOR_CHAR + "&\\p{XDigit}]{12}");
|
||||
|
||||
@Override
|
||||
public String reformat(String text) {
|
||||
if (!text.contains("&x") && !text.contains(EnumChatFormat.COLOR_CHAR + "x")) return text;
|
||||
String replaced = text;
|
||||
Matcher m = pattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String hexCode = m.group();
|
||||
String fixed = new String(new char[] {'#', hexCode.charAt(3), hexCode.charAt(5), hexCode.charAt(7), hexCode.charAt(9), hexCode.charAt(11), hexCode.charAt(13)});
|
||||
replaced = replaced.replace(hexCode, fixed);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.api.chat.rgb.format;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Formatter for {#RRGGBB}
|
||||
*/
|
||||
public class CMIFormat implements RGBFormatter {
|
||||
|
||||
private final Pattern pattern = Pattern.compile("\\{#[0-9a-fA-F]{6}}");
|
||||
|
||||
@Override
|
||||
public String reformat(String text) {
|
||||
if (!text.contains("{#")) return text;
|
||||
String replaced = text;
|
||||
Matcher m = pattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String hexCode = m.group();
|
||||
String fixed = hexCode.substring(2, 8);
|
||||
replaced = replaced.replace(hexCode, "#" + fixed);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.api.chat.rgb.format;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Formatter for #<RRGGBB>
|
||||
*/
|
||||
public class HtmlFormat implements RGBFormatter {
|
||||
|
||||
private final Pattern pattern = Pattern.compile("#<[0-9a-fA-F]{6}>");
|
||||
|
||||
@Override
|
||||
public String reformat(String text) {
|
||||
if (!text.contains("#<")) return text;
|
||||
Matcher m = pattern.matcher(text);
|
||||
String replaced = text;
|
||||
while (m.find()) {
|
||||
String hexCode = m.group();
|
||||
String fixed = hexCode.substring(2, 8);
|
||||
replaced = replaced.replace(hexCode, "#" + fixed);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.neznamy.tab.api.chat.rgb.format;
|
||||
|
||||
/**
|
||||
* Abstract class for different RGB patterns
|
||||
*/
|
||||
public interface RGBFormatter {
|
||||
|
||||
/**
|
||||
* Reformats RGB codes in provided text into #RRGGBB format
|
||||
* @param text - text to format
|
||||
* @return reformatted text
|
||||
*/
|
||||
String reformat(String text);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package me.neznamy.tab.api.chat.rgb.format;
|
||||
|
||||
/**
|
||||
* Formatter for &#RRGGBB
|
||||
*/
|
||||
public class UnnamedFormat1 implements RGBFormatter {
|
||||
|
||||
@Override
|
||||
public String reformat(String text) {
|
||||
return text.contains("&#") ? text.replace("&#", "#") : text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package me.neznamy.tab.api.chat.rgb.gradient;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class CMIGradient extends CommonGradient {
|
||||
|
||||
//pattern for {#RRGGBB<>}
|
||||
private final Pattern shortcutPattern = Pattern.compile("\\{#[0-9a-fA-F]{6}<>}");
|
||||
|
||||
public CMIGradient() {
|
||||
super(Pattern.compile("\\{#[0-9a-fA-F]{6}>}[^{]*\\{#[0-9a-fA-F]{6}<}"),
|
||||
Pattern.compile("\\{#[0-9a-fA-F]{6}\\|.>}[^{]*\\{#[0-9a-fA-F]{6}<}"),
|
||||
"{#", 9, 2, 10, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String applyPattern(String text, boolean ignorePlaceholders) {
|
||||
String replaced = text;
|
||||
if (replaced.contains("<>}")) {
|
||||
Matcher m = shortcutPattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String format = m.group();
|
||||
String code = format.substring(2, 8);
|
||||
replaced = replaced.replace(format, "{#" + code + "<}{#" + code + ">}");
|
||||
}
|
||||
}
|
||||
return super.applyPattern(replaced, ignorePlaceholders);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.neznamy.tab.api.chat.rgb.gradient;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.TextColor;
|
||||
|
||||
public class CommonGradient implements GradientPattern {
|
||||
|
||||
private final Pattern pattern;
|
||||
private final Pattern legacyPattern;
|
||||
private final String containCheck;
|
||||
private final int legacyCharPosition;
|
||||
private final int startColorStart;
|
||||
private final int messageStart;
|
||||
private final int endColorStartSub;
|
||||
|
||||
public CommonGradient(Pattern pattern, Pattern legacyPattern, String containCheck, int legacyCharPosition,
|
||||
int startColorStart, int messageStart, int endColorStartSub) {
|
||||
this.pattern = pattern;
|
||||
this.legacyPattern = legacyPattern;
|
||||
this.containCheck = containCheck;
|
||||
this.legacyCharPosition = legacyCharPosition;
|
||||
this.startColorStart = startColorStart;
|
||||
this.messageStart = messageStart;
|
||||
this.endColorStartSub = endColorStartSub;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String applyPattern(String text, boolean ignorePlaceholders) {
|
||||
if (!text.contains(containCheck)) return text;
|
||||
String replaced = text;
|
||||
Matcher m = legacyPattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String format = m.group();
|
||||
EnumChatFormat legacyColor = EnumChatFormat.getByChar(format.charAt(legacyCharPosition));
|
||||
if ((ignorePlaceholders && format.contains("%")) || legacyColor == null) continue;
|
||||
TextColor start = new TextColor(format.substring(startColorStart, startColorStart+6), legacyColor);
|
||||
String message = format.substring(messageStart+2, format.length()-10);
|
||||
TextColor end = new TextColor(format.substring(format.length()-endColorStartSub, format.length()-endColorStartSub+6));
|
||||
String applied = asGradient(start, message, end);
|
||||
replaced = replaced.replace(format, applied);
|
||||
}
|
||||
m = pattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String format = m.group();
|
||||
if (ignorePlaceholders && format.contains("%")) continue;
|
||||
TextColor start = new TextColor(format.substring(startColorStart, startColorStart+6));
|
||||
String message = format.substring(messageStart, format.length()-10);
|
||||
TextColor end = new TextColor(format.substring(format.length()-endColorStartSub, format.length()-endColorStartSub+6));
|
||||
String applied = asGradient(start, message, end);
|
||||
replaced = replaced.replace(format, applied);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package me.neznamy.tab.api.chat.rgb.gradient;
|
||||
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.TextColor;
|
||||
|
||||
/**
|
||||
* Abstract class for applying different gradient patterns
|
||||
*/
|
||||
public interface GradientPattern {
|
||||
|
||||
/**
|
||||
* Applies gradients in provided text and returns text using only #RRGGBB
|
||||
* @param text - text to be reformatted
|
||||
* @return reformatted text
|
||||
*/
|
||||
String applyPattern(String text, boolean ignorePlaceholders);
|
||||
|
||||
/**
|
||||
* Returns gradient text based on start color, text and end color
|
||||
* @param start - start color
|
||||
* @param text - text to be reformatted
|
||||
* @param end - end color
|
||||
* @return reformatted text
|
||||
*/
|
||||
default String asGradient(TextColor start, String text, TextColor end) {
|
||||
//lazy support for magic codes in gradients
|
||||
String magicCodes = EnumChatFormat.getLastColors(text);
|
||||
String deColorized = text.substring(magicCodes.length());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int length = deColorized.length();
|
||||
if (length == 1) {
|
||||
sb.append("#");
|
||||
sb.append(new TextColor(start.getRed(), start.getGreen(), start.getBlue()).getHexCode());
|
||||
if (start.isLegacyColorForced()) sb.append("|").append(start.getLegacyColor().getCharacter());
|
||||
sb.append(magicCodes);
|
||||
sb.append(deColorized);
|
||||
return sb.toString();
|
||||
}
|
||||
for (int i=0; i<length; i++) {
|
||||
int red = (int) (start.getRed() + (float)(end.getRed() - start.getRed())/(length-1)*i);
|
||||
int green = (int) (start.getGreen() + (float)(end.getGreen() - start.getGreen())/(length-1)*i);
|
||||
int blue = (int) (start.getBlue() + (float)(end.getBlue() - start.getBlue())/(length-1)*i);
|
||||
sb.append("#");
|
||||
sb.append(new TextColor(red, green, blue).getHexCode());
|
||||
if (start.isLegacyColorForced()) sb.append("|").append(start.getLegacyColor().getCharacter());
|
||||
sb.append(magicCodes);
|
||||
sb.append(deColorized.charAt(i));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package me.neznamy.tab.api.chat.rgb.gradient;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.TextColor;
|
||||
|
||||
/**
|
||||
* Gradient applier for <gradient:#RRGGBB:#RRGGBB>Text</gradient>
|
||||
* and <gradient:#RRGGBB|L:#RRGGBB>Text</gradient>
|
||||
*/
|
||||
public class KyoriGradient implements GradientPattern {
|
||||
|
||||
private final Pattern pattern = Pattern.compile("<gradient:#[0-9a-fA-F]{6}:#[0-9a-fA-F]{6}>[^<]*</gradient>");
|
||||
|
||||
private final Pattern patternLegacy = Pattern.compile("<gradient:#[0-9a-fA-F]{6}\\|.:#[0-9a-fA-F]{6}>[^<]*</gradient>");
|
||||
|
||||
@Override
|
||||
public String applyPattern(String text, boolean ignorePlaceholders) {
|
||||
if (!text.contains("<grad")) return text;
|
||||
String replaced = text;
|
||||
Matcher m = patternLegacy.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String format = m.group();
|
||||
EnumChatFormat legacyColor = EnumChatFormat.getByChar(format.charAt(18));
|
||||
if ((ignorePlaceholders && format.contains("%")) || legacyColor == null) continue;
|
||||
TextColor start = new TextColor(format.substring(11, 17), legacyColor);
|
||||
String message = format.substring(28, format.length()-11);
|
||||
TextColor end = new TextColor(format.substring(21, 27));
|
||||
String applied = asGradient(start, message, end);
|
||||
replaced = replaced.replace(format, applied);
|
||||
}
|
||||
m = pattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String format = m.group();
|
||||
if (ignorePlaceholders && format.contains("%")) continue;
|
||||
TextColor start = new TextColor(format.substring(11, 17));
|
||||
String message = format.substring(26, format.length()-11);
|
||||
TextColor end = new TextColor(format.substring(19, 25));
|
||||
String applied = asGradient(start, message, end);
|
||||
replaced = replaced.replace(format, applied);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
package me.neznamy.tab.api.config;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* Abstract class for configuration file
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public abstract class ConfigurationFile {
|
||||
|
||||
/** Comments on top of the file */
|
||||
protected List<String> header;
|
||||
|
||||
/** Configuration file content */
|
||||
protected Map<String, Object> values;
|
||||
|
||||
/** File to use */
|
||||
protected final File file;
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load specified configuration file.
|
||||
* If file does not exist, default file is copied from {@code source}.
|
||||
*
|
||||
* @param source
|
||||
* Source to copy file from if it does not exist
|
||||
* @param destination
|
||||
* File destination to use
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code destination} is null
|
||||
* @throws IllegalStateException
|
||||
* if file does not exist and source is null
|
||||
* @throws IOException
|
||||
* if I/O operation with the file unexpectedly fails
|
||||
*/
|
||||
protected ConfigurationFile(InputStream source, File destination) throws IOException {
|
||||
Preconditions.checkNotNull(destination, "destination");
|
||||
this.file = destination;
|
||||
if (file.getParentFile() != null && !file.getParentFile().exists()) Files.createDirectories(file.getParentFile().toPath());
|
||||
if (!file.exists() && source == null) throw new IllegalStateException("File does not exist and source is null");
|
||||
if (file.createNewFile()) {
|
||||
// Files.copy(source, file.toPath());
|
||||
//avoiding MalformedInputException thrown on fabric due to file not having UTF8 encoding by default
|
||||
//also automatically using the encoding so users don't have to worry about it anymore
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(source, StandardCharsets.UTF_8));
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))){
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
writer.write(line + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
detectHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves values from map to the file
|
||||
*/
|
||||
public abstract void save();
|
||||
|
||||
/**
|
||||
* Returns simple name of the file
|
||||
* @return simple name of the file
|
||||
*/
|
||||
public String getName() {
|
||||
return file.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root value map
|
||||
* @return the root value map
|
||||
*/
|
||||
public Map<String, Object> getValues(){
|
||||
return values;
|
||||
}
|
||||
|
||||
public void setValues(Map<String, Object> values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets config option with specified path. If the option is not present and
|
||||
* {@code defaultValue} is not {@code null}, value is inserted, {@link #save()}
|
||||
* called and {@code defaultValue} returned.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @param defaultValue
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file
|
||||
*/
|
||||
public Object getObject(String path, Object defaultValue) {
|
||||
Object value = values;
|
||||
for (String section : path.contains(".") ? path.split("\\.") : new String[] {path}) {
|
||||
if (!(value instanceof Map)) {
|
||||
if (defaultValue != null) set(path, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
value = getIgnoreCase((Map<Object, Object>) value, section);
|
||||
}
|
||||
if (value == null && defaultValue != null) {
|
||||
TabAPI.getInstance().debug("Inserting missing config option \"" + path + "\" with value \"" + defaultValue + "\" into " + file.getName());
|
||||
set(path, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path. If option is not present,
|
||||
* {@code null} is returned.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @return value from configuration file or null if not present
|
||||
*/
|
||||
public Object getObject(String path) {
|
||||
return getObject(path, null);
|
||||
}
|
||||
|
||||
public Object getObject(String[] path) {
|
||||
Object value = values;
|
||||
for (String section : path) {
|
||||
if (!(value instanceof Map)) {
|
||||
return null;
|
||||
}
|
||||
value = getIgnoreCase((Map<Object, Object>) value, section);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value from map without case sensitivity of the key. Returns
|
||||
* {@code null} if no such key is found.
|
||||
*
|
||||
* @param map
|
||||
* map to get value from
|
||||
* @param key
|
||||
* case-insensitive key name
|
||||
* @return map value from case-insensitive key
|
||||
*/
|
||||
private Object getIgnoreCase(Map<Object, Object> map, String key) {
|
||||
for (Entry<Object, Object> entry : map.entrySet()) {
|
||||
if (entry.getKey().toString().equalsIgnoreCase(key)) return entry.getValue();
|
||||
}
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code String}. Returns {@code null} if
|
||||
* option is not present.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @return value from file or null if not present
|
||||
*/
|
||||
public String getString(String path) {
|
||||
return getString(path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code String}. If the option is not present
|
||||
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
|
||||
* and {@code defaultValue} returned.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @param defaultValue
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file as {@code String}
|
||||
*/
|
||||
public String getString(String path, String defaultValue) {
|
||||
Object value = getObject(path, defaultValue);
|
||||
if (value == null) return defaultValue;
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code List<String>}. Returns {@code null} if
|
||||
* option is not present.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @return value from file or null if not present
|
||||
*/
|
||||
public List<String> getStringList(String path) {
|
||||
return getStringList(path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code List<String>}. If the option is not present
|
||||
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
|
||||
* and {@code defaultValue} returned.
|
||||
*
|
||||
* @param path
|
||||
* Path of the option with sections separated with "{@code .}"
|
||||
* @param defaultValue
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file as {@code List<String>}
|
||||
*/
|
||||
public List<String> getStringList(String path, List<String> defaultValue) {
|
||||
Object value = getObject(path, defaultValue);
|
||||
if (value == null) return defaultValue;
|
||||
if (!(value instanceof List)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<String> fixedList = new ArrayList<>();
|
||||
for (Object key : (List<Object>)value) {
|
||||
fixedList.add(key.toString());
|
||||
}
|
||||
return fixedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code Integer}. Returns {@code null} if
|
||||
* option is not present.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @return value from file or null if not present
|
||||
*/
|
||||
public Integer getInt(String path) {
|
||||
return getInt(path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code Integer}. If the option is not present
|
||||
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
|
||||
* and {@code defaultValue} returned.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @param defaultValue
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file as {@code Integer}
|
||||
*/
|
||||
public Integer getInt(String path, Integer defaultValue) {
|
||||
Object value = getObject(path, defaultValue);
|
||||
if (value == null) return defaultValue;
|
||||
try{
|
||||
return Integer.parseInt(value.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code Boolean}. If the option is not present
|
||||
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
|
||||
* and {@code defaultValue} returned.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @param defaultValue
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file as {@code Boolean}
|
||||
*/
|
||||
public boolean getBoolean(String path, boolean defaultValue) {
|
||||
Object value = getObject(path, defaultValue);
|
||||
if (value == null) return defaultValue;
|
||||
return Boolean.parseBoolean(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code Double}. If the option is not present
|
||||
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
|
||||
* and {@code defaultValue} returned.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @param defaultValue
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file as {@code Double}
|
||||
*/
|
||||
public Double getDouble(String path, double defaultValue) {
|
||||
Object value = getObject(path, defaultValue);
|
||||
if (value == null) return defaultValue;
|
||||
try {
|
||||
return Double.parseDouble(value.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code Map<K, V>}. If the option
|
||||
* is not present or value is not a {@code Map}, new empty {@code Map} is returned.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @return value from configuration file as {@code Map<K, V>}
|
||||
*/
|
||||
public <K, V> Map<K, V> getConfigurationSection(String path) {
|
||||
if (path == null || path.length() == 0) return (Map<K, V>) values;
|
||||
Object value = getObject(path, null);
|
||||
if (value instanceof Map) {
|
||||
return (Map<K, V>) value;
|
||||
} else {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the file has option with specified path, {@code false}
|
||||
* if not.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @return {@code true} if present, {@code false} if not
|
||||
*/
|
||||
public boolean hasConfigOption(String path) {
|
||||
return getObject(path) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value to the specified path and saves the file to disk by calling {@link #save()}.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @param value
|
||||
* Value to save
|
||||
*/
|
||||
public void set(String path, Object value) {
|
||||
set(values, path, value);
|
||||
save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value to the specified map with specified path and value. This is an internal method
|
||||
* that correctly creates maps to separate sections using "{@code .}".
|
||||
*
|
||||
* @param map
|
||||
* Map to insert value to
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @param value
|
||||
* Value to save
|
||||
* @return the first argument to allow chaining
|
||||
*/
|
||||
private Map<String, Object> set(Map<String, Object> map, String path, Object value) {
|
||||
if (path.contains(".")) {
|
||||
String keyWord = getRealKey(map, path.split("\\.")[0]);
|
||||
Object subMap = map.get(keyWord);
|
||||
if (!(subMap instanceof Map)) {
|
||||
subMap = new LinkedHashMap<>();
|
||||
}
|
||||
map.put(keyWord, set((Map<String, Object>) subMap, path.substring(keyWord.length()+1), value));
|
||||
} else {
|
||||
if (value == null) {
|
||||
map.remove(getRealKey(map, path));
|
||||
} else {
|
||||
map.put(path, value);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the real key name without case sensitivity from map. If not found, inserted key
|
||||
* is returned.
|
||||
*
|
||||
* @param map
|
||||
* Map to check keys of
|
||||
* @param key
|
||||
* Key to find
|
||||
* @return The real key name
|
||||
*/
|
||||
private String getRealKey(Map<?, ?> map, String key) {
|
||||
for (Object mapKey : map.keySet()) {
|
||||
if (mapKey.toString().equalsIgnoreCase(key)) return mapKey.toString();
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects header of a file (first lines of file starting with #).
|
||||
*
|
||||
* @throws IOException
|
||||
* if I/O operation fails
|
||||
*/
|
||||
private void detectHeader() throws IOException {
|
||||
header = new ArrayList<>();
|
||||
for (String line : Files.readAllLines(file.toPath())) {
|
||||
if (line.startsWith("#")) {
|
||||
header.add(line);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts header back into file. This is required after calling {@link #save()}, because
|
||||
* it destroys the header.
|
||||
*
|
||||
* @throws IOException
|
||||
* if I/O operation fails
|
||||
*/
|
||||
public void fixHeader() throws IOException {
|
||||
if (header == null) return;
|
||||
List<String> content = new ArrayList<>(header);
|
||||
content.addAll(Files.readAllLines(file.toPath()));
|
||||
Files.delete(file.toPath());
|
||||
if (file.createNewFile()) {
|
||||
Files.write(file.toPath(), content);
|
||||
}
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package me.neznamy.tab.api.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.error.YAMLException;
|
||||
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.yamlassist.YamlAssist;
|
||||
|
||||
/**
|
||||
* YAML implementation of ConfigurationFile
|
||||
*/
|
||||
public class YamlConfigurationFile extends ConfigurationFile {
|
||||
|
||||
/** SnakeYAML instance */
|
||||
private static final Yaml yaml;
|
||||
|
||||
static {
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
yaml = new Yaml(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load specified configuration file.
|
||||
* If file does not exist, default file is copied from {@code source}.
|
||||
*
|
||||
* @param source
|
||||
* Source to copy file from if it does not exist
|
||||
* @param destination
|
||||
* File destination to use
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code destination} is null
|
||||
* @throws IllegalStateException
|
||||
* if file does not exist and source is null
|
||||
* @throws YAMLException
|
||||
* if file has invalid YAML syntax
|
||||
* @throws IOException
|
||||
* if I/O operation with the file unexpectedly fails
|
||||
*/
|
||||
public YamlConfigurationFile(InputStream source, File destination) throws YAMLException, IOException {
|
||||
super(source, destination);
|
||||
FileInputStream input = null;
|
||||
try {
|
||||
input = new FileInputStream(file);
|
||||
values = yaml.load(input);
|
||||
if (values == null) values = new LinkedHashMap<>();
|
||||
input.close();
|
||||
} catch (YAMLException e) {
|
||||
if (input != null) input.close();
|
||||
TabAPI tab = TabAPI.getInstance();
|
||||
tab.setBrokenFile(destination.getName());
|
||||
tab.sendConsoleMessage("&cFile " + destination + " has broken syntax.", true);
|
||||
tab.sendConsoleMessage("&6Error message from yaml parser: " + e.getMessage(), true);
|
||||
List<String> suggestions = YamlAssist.getSuggestions(file);
|
||||
if (!suggestions.isEmpty()) {
|
||||
tab.sendConsoleMessage("&dSuggestions to fix yaml syntax:", true);
|
||||
for (String suggestion : suggestions) {
|
||||
tab.sendConsoleMessage("&d- " + suggestion, true);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
try {
|
||||
Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
|
||||
yaml.dump(values, writer);
|
||||
writer.close();
|
||||
fixHeader();
|
||||
} catch (IOException e) {
|
||||
TabAPI.getInstance().sendConsoleMessage("&cFailed to save yaml file " + file.getPath() + " with content " + values.toString(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
/**
|
||||
* The event bus. This is used to register event listeners/handlers for
|
||||
* handling events that may be fired by TAB.
|
||||
*/
|
||||
public interface EventBus {
|
||||
|
||||
/**
|
||||
* Scans the given listener for any method that is annotated with
|
||||
* {@link Subscribe} and registers it as a listener to this event bus.
|
||||
*
|
||||
* All methods in this class that are annotated with {@link Subscribe}
|
||||
* must meet the following criteria:
|
||||
* <ul>
|
||||
* <li>It must be <strong>public</strong>.</li>
|
||||
* <li>It must <strong>not</strong> be abstract.</li>
|
||||
* <li>It must have exactly <strong>one</strong> parameter.</li>
|
||||
* <li>Its single parameter must be <strong>an event</strong>.</li>
|
||||
* </ul>
|
||||
* If a method does not meet the above criteria, an exception <strong>will</strong>
|
||||
* be thrown.
|
||||
*
|
||||
* @param listener the listener to register
|
||||
* @throws RuntimeException if a method does not meet the criteria
|
||||
*/
|
||||
void register(Object listener);
|
||||
|
||||
/**
|
||||
* Registers the given handler to this event bus.
|
||||
*
|
||||
* @param type the class type of the event
|
||||
* @param handler the handler to register
|
||||
* @param <E> the type of the event
|
||||
*/
|
||||
<E extends TabEvent> void register(Class<E> type, EventHandler<E> handler);
|
||||
|
||||
/**
|
||||
* Scans the given listener for any method that is annotated with
|
||||
* {@link Subscribe} and unregisters it from this event bus.
|
||||
*
|
||||
* @param listener the listener to unregister
|
||||
*/
|
||||
void unregister(Object listener);
|
||||
|
||||
/**
|
||||
* Unregisters the given handler from this event bus.
|
||||
*
|
||||
* @param handler the handler to unregister
|
||||
* @param <E> the type of the event
|
||||
*/
|
||||
<E extends TabEvent> void unregister(EventHandler<E> handler);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
/**
|
||||
* A handler for an event. This allows listening for events in a more
|
||||
* functional style.
|
||||
*
|
||||
* @param <E> the type of event this handler handles
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EventHandler<E> {
|
||||
|
||||
/**
|
||||
* Called when the given event is fired from an event bus.
|
||||
*
|
||||
* @param event the event to handle
|
||||
*/
|
||||
void handle(E event);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Subscribes to a {@link TabEvent}.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Subscribe {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
/**
|
||||
* An event that may be fired by TAB when something occurs.
|
||||
*
|
||||
* This is a marker interface. All events <strong>must</strong> implement this
|
||||
* interface.
|
||||
*/
|
||||
public interface TabEvent {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.neznamy.tab.api.event.player;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
|
||||
/**
|
||||
* Called when the player has been fully loaded by TAB.
|
||||
*/
|
||||
public interface PlayerLoadEvent extends TabEvent {
|
||||
|
||||
/**
|
||||
* Gets the player that was loaded.
|
||||
*
|
||||
* @return the player that was loaded
|
||||
*/
|
||||
TabPlayer getPlayer();
|
||||
|
||||
/**
|
||||
* Returns true if player was loaded because they joined,
|
||||
* false if the player was loaded because tab reload command was executed
|
||||
* @return true if player joined, false if plugin was reloaded
|
||||
*/
|
||||
boolean isJoin();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package me.neznamy.tab.api.event.plugin;
|
||||
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
import me.neznamy.tab.api.placeholder.Placeholder;
|
||||
|
||||
public interface PlaceholderRegisterEvent extends TabEvent {
|
||||
|
||||
String getIdentifier();
|
||||
|
||||
void setPlaceholder(Placeholder placeholder);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.neznamy.tab.api.event.plugin;
|
||||
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
|
||||
/**
|
||||
* Called when TAB is fully loaded.
|
||||
*/
|
||||
public interface TabLoadEvent extends TabEvent {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
public interface Placeholder {
|
||||
|
||||
int getRefresh();
|
||||
|
||||
String getIdentifier();
|
||||
|
||||
boolean isTriggerMode();
|
||||
|
||||
void enableTriggerMode();
|
||||
|
||||
void enableTriggerMode(Runnable onActivation, Runnable onDisable);
|
||||
|
||||
void unload();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
public interface PlaceholderManager {
|
||||
|
||||
/**
|
||||
* Registers a server placeholder (placeholder with same output for all players)
|
||||
*/
|
||||
ServerPlaceholder registerServerPlaceholder(String identifier, int refresh, Supplier<Object> supplier);
|
||||
|
||||
/**
|
||||
* Registers a player placeholder (placeholder with player-specific output)
|
||||
*/
|
||||
PlayerPlaceholder registerPlayerPlaceholder(String identifier, int refresh, Function<TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Registers a relational placeholder (different output for each player pair)
|
||||
*/
|
||||
RelationalPlaceholder registerRelationalPlaceholder(String identifier, int refresh, BiFunction<TabPlayer, TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Detects placeholders in text using %% pattern and returns list of all detected identifiers
|
||||
* @param text - text to detect placeholders in
|
||||
* @return list of detected identifiers
|
||||
*/
|
||||
List<String> detectPlaceholders(String text);
|
||||
|
||||
/**
|
||||
* Adds placeholder to list of used placeholders and assigns this feature as using it,
|
||||
* which will then receive refresh() if values changes
|
||||
* @param identifier - placeholder identifier
|
||||
* @param feature - feature using the placeholder
|
||||
*/
|
||||
void addUsedPlaceholder(String identifier, TabFeature feature);
|
||||
|
||||
String findReplacement(String placeholder, String output);
|
||||
|
||||
List<String> getUsedPlaceholders();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
public interface PlayerPlaceholder extends Placeholder {
|
||||
|
||||
void updateValue(TabPlayer player, Object value);
|
||||
|
||||
/**
|
||||
* Calls the placeholder request function and returns the output.
|
||||
* If the placeholder thrown an exception, it is logged in {@code placeholder-errors.log}
|
||||
* file and "ERROR" is returned.
|
||||
* @param p
|
||||
* player to get placeholder value for
|
||||
* @return value placeholder returned or "ERROR" if it threw an error
|
||||
*/
|
||||
Object request(TabPlayer p);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
public interface RelationalPlaceholder extends Placeholder {
|
||||
|
||||
void updateValue(TabPlayer viewer, TabPlayer target, Object value);
|
||||
|
||||
Object request(TabPlayer viewer, TabPlayer target);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
public interface ServerPlaceholder extends Placeholder {
|
||||
|
||||
void updateValue(Object value);
|
||||
|
||||
Object request();
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.api.util.BiFunctionWithException;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class for packet building, methods are overridden in
|
||||
* platform-specific builders which extend this class to build actual packets.
|
||||
* Packets which don't have a method override are forwarded into
|
||||
* sendPacket method, where they are handled using platform's API instead.
|
||||
*/
|
||||
public class PacketBuilder {
|
||||
|
||||
/** Function map turning custom packet class into platform-specific packets */
|
||||
protected final Map<Class<? extends TabPacket>, BiFunctionWithException<TabPacket, ProtocolVersion, Object>> buildMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance and fills {@link #buildMap} with abstract build methods
|
||||
* of packets present on all platforms.
|
||||
*/
|
||||
public PacketBuilder() {
|
||||
buildMap.put(PacketPlayOutBoss.class, (packet, version) -> build((PacketPlayOutBoss)packet, version));
|
||||
buildMap.put(PacketPlayOutChat.class, (packet, version) -> build((PacketPlayOutChat)packet, version));
|
||||
buildMap.put(PacketPlayOutPlayerInfo.class, (packet, version) -> build((PacketPlayOutPlayerInfo)packet, version));
|
||||
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> build((PacketPlayOutPlayerListHeaderFooter)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> build((PacketPlayOutScoreboardDisplayObjective)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> build((PacketPlayOutScoreboardObjective)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> build((PacketPlayOutScoreboardScore)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> build((PacketPlayOutScoreboardTeam)packet, version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts custom packet into platform-specific packet by calling a function from
|
||||
* {@link #buildMap}.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to build
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if reflection fails
|
||||
*/
|
||||
public Object build(TabPacket packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
Preconditions.checkNotNull(packet, "packet");
|
||||
Preconditions.checkNotNull(clientVersion, "clientVersion");
|
||||
return buildMap.get(packet.getClass()).apply(packet, clientVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutBoss class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutChat class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutPlayerInfo class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutPlayerListHeaderFooter class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardDisplayObjective class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardObjective class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardScore class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardTeam class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cuts given string to specified character length (or length-1 if last character is a color character)
|
||||
* and translates RGB to legacy colors. If string is not that long, the original string is returned.
|
||||
* RGB codes are converted into legacy, since cutting is only needed for <1.13.
|
||||
* If {@code string} is {@code null}, empty string is returned.
|
||||
* @param string
|
||||
* String to cut
|
||||
* @param length
|
||||
* Length to cut to
|
||||
* @return string cut to {@code length} characters
|
||||
*/
|
||||
public String cutTo(String string, int length) {
|
||||
if (string == null) return "";
|
||||
String legacyText = string;
|
||||
if (string.contains("#")) {
|
||||
//converting RGB to legacy colors
|
||||
legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
|
||||
}
|
||||
if (legacyText.length() <= length) return legacyText;
|
||||
if (legacyText.charAt(length-1) == EnumChatFormat.COLOR_CHAR) {
|
||||
return legacyText.substring(0, length-1); //cutting one extra character to prevent prefix ending with "&"
|
||||
} else {
|
||||
return legacyText.substring(0, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
|
||||
* it as a serialized component, which BungeeCord uses.
|
||||
* <p>
|
||||
* If {@code clientVersion} is < 1.12, the text is cut to {@code length} characters if
|
||||
* needed and returned.
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @param clientVersion
|
||||
* Version of player to convert text for
|
||||
* @return serialized component for 1.13+ clients, cut string for 1.12-
|
||||
*/
|
||||
public String jsonOrCut(String text, ProtocolVersion clientVersion, int length) {
|
||||
if (text == null) return null;
|
||||
if (clientVersion.getMinorVersion() >= 13) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
|
||||
} else {
|
||||
return cutTo(text, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts platform-specific instance of player info packet into
|
||||
* {@link PacketPlayOutPlayerInfo} object.
|
||||
*
|
||||
* @param packet
|
||||
* platform-specific info packet
|
||||
* @param clientVersion
|
||||
* Version of client receiving the packet
|
||||
* @return The packet converted into {@link PacketPlayOutPlayerInfo}
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo readPlayerInfo(Object packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts platform-specific instance of objective packet into
|
||||
* {@link PacketPlayOutScoreboardObjective} object.
|
||||
*
|
||||
* @param packet
|
||||
* platform-specific objective packet
|
||||
* @return The packet converted into {@link PacketPlayOutScoreboardObjective}
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public PacketPlayOutScoreboardObjective readObjective(Object packet) throws ReflectiveOperationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts platform-specific instance of display objective packet into
|
||||
* {@link PacketPlayOutScoreboardDisplayObjective} object.
|
||||
*
|
||||
* @param packet
|
||||
* platform-specific display objective packet
|
||||
* @return The packet converted into {@link PacketPlayOutScoreboardDisplayObjective}
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object packet) throws ReflectiveOperationException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutBoss implements TabPacket {
|
||||
|
||||
/** UUID of the BossBar */
|
||||
private final UUID id;
|
||||
|
||||
/** Action of this packet */
|
||||
private final Action action;
|
||||
|
||||
/** BossBar title */
|
||||
private String name;
|
||||
|
||||
/** BossBar progress (0-1)*/
|
||||
private float pct;
|
||||
|
||||
/** BossBar color */
|
||||
private BarColor color;
|
||||
|
||||
/** BossBar style */
|
||||
private BarStyle overlay;
|
||||
|
||||
/** Darken screen flag */
|
||||
private boolean darkenScreen;
|
||||
|
||||
/** Play music flag */
|
||||
private boolean playMusic;
|
||||
|
||||
/** Create fog flag */
|
||||
private boolean createWorldFog;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.ADD action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param name
|
||||
* BossBar title
|
||||
* @param pct
|
||||
* BossBar progress
|
||||
* @param color
|
||||
* BossBar color
|
||||
* @param overlay
|
||||
* BossBar style
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id, String name, float pct, BarColor color, BarStyle overlay) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
Preconditions.checkNotNull(name, "title");
|
||||
Preconditions.checkNotNull(color, "color");
|
||||
Preconditions.checkNotNull(overlay, "style");
|
||||
this.action = Action.ADD;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.pct = pct;
|
||||
this.color = color;
|
||||
this.overlay = overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.REMOVE action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
this.action = Action.REMOVE;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PCT action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param pct
|
||||
* BossBar progress
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id, float pct) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
this.action = Action.UPDATE_PCT;
|
||||
this.id = id;
|
||||
this.pct = pct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_NAME action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param name
|
||||
* BossBar title
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id, String name) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
Preconditions.checkNotNull(name, "title");
|
||||
this.action = Action.UPDATE_NAME;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_STYLE action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param color
|
||||
* BossBar color
|
||||
* @param overlay
|
||||
* BossBar style
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id, BarColor color, BarStyle overlay) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
Preconditions.checkNotNull(color, "color");
|
||||
Preconditions.checkNotNull(overlay, "style");
|
||||
this.action = Action.UPDATE_STYLE;
|
||||
this.id = id;
|
||||
this.color = color;
|
||||
this.overlay = overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PROPERTIES action
|
||||
*
|
||||
* @param darkenScreen
|
||||
* Darken screen flag
|
||||
* @param playMusic
|
||||
* Play music flag
|
||||
* @param createWorldFog
|
||||
* Create fog flag
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
this.action = Action.UPDATE_PROPERTIES;
|
||||
this.id = id;
|
||||
this.darkenScreen = darkenScreen;
|
||||
this.playMusic = playMusic;
|
||||
this.createWorldFog = createWorldFog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bitmask based on {@link #darkenScreen}, {@link #playMusic} and {@link #darkenScreen} values.
|
||||
* <p>
|
||||
* {@link #darkenScreen} adds {@code 1}, {@link #playMusic} {@code 2} and {@link #darkenScreen} {@code 4}
|
||||
* to the final value.
|
||||
*
|
||||
* @return the bitmask
|
||||
*/
|
||||
public byte getFlags(){
|
||||
byte value = 0;
|
||||
if (darkenScreen) value += 1;
|
||||
if (playMusic) value += 2;
|
||||
if (createWorldFog) value += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PacketPlayOutBoss{id=%s,operation=%s,name=%s,pct=%s,color=%s,overlay=%s,darkenScreen=%s,playMusic=%s,createWorldFog=%s}",
|
||||
id, action, name, pct, color, overlay, darkenScreen, playMusic, createWorldFog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #color}
|
||||
* @return color
|
||||
*/
|
||||
public BarColor getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #overlay}
|
||||
* @return style
|
||||
*/
|
||||
public BarStyle getOverlay() {
|
||||
return overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #name}
|
||||
* @return name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #id}
|
||||
* @return id
|
||||
*/
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #pct}
|
||||
* @return progress
|
||||
*/
|
||||
public float getPct() {
|
||||
return pct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #action}
|
||||
* @return packet action
|
||||
*/
|
||||
public Action getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #darkenScreen}
|
||||
* @return darkenScreen
|
||||
*/
|
||||
public boolean isDarkenScreen() {
|
||||
return darkenScreen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #createWorldFog}
|
||||
* @return createWorldFog
|
||||
*/
|
||||
public boolean isCreateWorldFog() {
|
||||
return createWorldFog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #playMusic}
|
||||
* @return playMusic
|
||||
*/
|
||||
public boolean isPlayMusic() {
|
||||
return playMusic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #darkenScreen} to specified value
|
||||
*
|
||||
* @param darkenScreen
|
||||
* Darken screen flag
|
||||
*/
|
||||
public void setDarkenScreen(boolean darkenScreen) {
|
||||
this.darkenScreen = darkenScreen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #createWorldFog} to specified value
|
||||
*
|
||||
* @param createWorldFog
|
||||
* Create fog flag
|
||||
*/
|
||||
public void setCreateWorldFog(boolean createWorldFog) {
|
||||
this.createWorldFog = createWorldFog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #playMusic} to specified value
|
||||
*
|
||||
* @param playMusic
|
||||
* Play music flag
|
||||
*/
|
||||
public void setPlayMusic(boolean playMusic) {
|
||||
this.playMusic = playMusic;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum representing all valid boss packet actions.
|
||||
* Calling ordinal() will return action's network ID.
|
||||
*/
|
||||
public enum Action {
|
||||
|
||||
ADD,
|
||||
REMOVE,
|
||||
UPDATE_PCT,
|
||||
UPDATE_NAME,
|
||||
UPDATE_STYLE,
|
||||
UPDATE_PROPERTIES
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutChat implements TabPacket {
|
||||
|
||||
/** Message to be sent */
|
||||
private final IChatBaseComponent message;
|
||||
|
||||
/** Message position */
|
||||
private final ChatMessageType type;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
*
|
||||
* @param message
|
||||
* Chat message to be sent
|
||||
* @param type
|
||||
* Message position
|
||||
*/
|
||||
public PacketPlayOutChat(IChatBaseComponent message, ChatMessageType type) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions.checkNotNull(type, "message type");
|
||||
this.message = message;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PacketPlayOutChat{message=%s,type=%s}", message, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #message}
|
||||
* @return message
|
||||
*/
|
||||
public IChatBaseComponent getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #type}
|
||||
* @return type
|
||||
*/
|
||||
public ChatMessageType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum representing positions of a chat message
|
||||
* Calling ordinal() will return type's network ID.
|
||||
*/
|
||||
public enum ChatMessageType {
|
||||
|
||||
CHAT,
|
||||
SYSTEM,
|
||||
GAME_INFO
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutPlayerInfo implements TabPacket {
|
||||
|
||||
/** Packet action */
|
||||
private final EnumPlayerInfoAction action;
|
||||
|
||||
/** List of affected entries */
|
||||
private final List<PlayerInfoData> entries;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param action
|
||||
* Packet action
|
||||
* @param entries
|
||||
* Affected entries
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, PlayerInfoData... entries) {
|
||||
Preconditions.checkNotNull(action, "action");
|
||||
Preconditions.checkNotNull(entries, "entries");
|
||||
this.action = action;
|
||||
this.entries = Arrays.asList(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param action
|
||||
* Packet action
|
||||
* @param entries
|
||||
* Affected entries
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, List<PlayerInfoData> entries) {
|
||||
Preconditions.checkNotNull(action, "action");
|
||||
Preconditions.checkNotNull(entries, "entries");
|
||||
this.action = action;
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PacketPlayOutPlayerInfo{action=%s,entries=%s}", action, entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #action}
|
||||
* @return packet action
|
||||
*/
|
||||
public EnumPlayerInfoAction getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #entries}
|
||||
* @return affected entries
|
||||
*/
|
||||
public List<PlayerInfoData> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* A subclass representing player list entry
|
||||
*/
|
||||
public static class PlayerInfoData {
|
||||
|
||||
/** Latency */
|
||||
private int latency;
|
||||
|
||||
/** GameMode */
|
||||
private EnumGamemode gameMode = EnumGamemode.SURVIVAL; //ProtocolLib causes NPE even when action does not use GameMode
|
||||
|
||||
/**
|
||||
* Display name displayed in TabList. Using {@code null} results in no display name
|
||||
* and scoreboard team prefix/suffix being visible in TabList instead.
|
||||
*/
|
||||
private IChatBaseComponent displayName;
|
||||
|
||||
/** Real name of affected player */
|
||||
private String name;
|
||||
|
||||
/** Player UUID */
|
||||
private UUID uniqueId;
|
||||
|
||||
/** Player's skin, null for empty skin */
|
||||
private Skin skin;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Suitable for
|
||||
* {@link EnumPlayerInfoAction}.ADD_PLAYER action
|
||||
*
|
||||
* @param name
|
||||
* Player's name
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
* @param skin
|
||||
* Player's platform-specific skin object
|
||||
* @param latency
|
||||
* Player's ping
|
||||
* @param gameMode
|
||||
* Player's GameMode
|
||||
* @param displayName
|
||||
* Player's display name
|
||||
*/
|
||||
public PlayerInfoData(String name, UUID uniqueId, Skin skin, int latency, EnumGamemode gameMode, IChatBaseComponent displayName) {
|
||||
Preconditions.checkNotNull(uniqueId, "uuid");
|
||||
this.name = name;
|
||||
this.uniqueId = uniqueId;
|
||||
this.skin = skin;
|
||||
this.latency = latency;
|
||||
this.gameMode = gameMode;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Suitable for
|
||||
* {@link EnumPlayerInfoAction}.UPDATE_GAME_MODE action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
* @param gameMode
|
||||
* Player's GameMode
|
||||
*/
|
||||
public PlayerInfoData(UUID uniqueId, EnumGamemode gameMode) {
|
||||
Preconditions.checkNotNull(uniqueId, "uuid");
|
||||
this.uniqueId = uniqueId;
|
||||
this.gameMode = gameMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Suitable for
|
||||
* {@link EnumPlayerInfoAction}.UPDATE_LATENCY action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
* @param latency
|
||||
* Player's ping
|
||||
*/
|
||||
public PlayerInfoData(UUID uniqueId, int latency) {
|
||||
Preconditions.checkNotNull(uniqueId, "uuid");
|
||||
this.uniqueId = uniqueId;
|
||||
this.latency = latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Suitable for
|
||||
* {@link EnumPlayerInfoAction}.UPDATE_DISPLAY_NAME action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
* @param displayName
|
||||
* Player's display name
|
||||
*/
|
||||
public PlayerInfoData(UUID uniqueId, IChatBaseComponent displayName) {
|
||||
Preconditions.checkNotNull(uniqueId, "uuid");
|
||||
this.uniqueId = uniqueId;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter. Suitable for
|
||||
* {@link EnumPlayerInfoAction}.REMOVE_PLAYER action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
*/
|
||||
public PlayerInfoData(UUID uniqueId) {
|
||||
this.uniqueId = uniqueId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PlayerInfoData{latency=%s,gameMode=%s,displayName=%s,name=%s,uniqueId=%s,skin=%s}",
|
||||
latency, gameMode, displayName, name, uniqueId, skin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #latency}
|
||||
* @return latency
|
||||
*/
|
||||
public int getLatency() {
|
||||
return latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #latency} to specified value
|
||||
* @param latency
|
||||
* Latency to use
|
||||
*/
|
||||
public void setLatency(int latency) {
|
||||
this.latency = latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #displayName}
|
||||
* @return displayName
|
||||
*/
|
||||
public IChatBaseComponent getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #displayName} to specified value
|
||||
* @param displayName
|
||||
* Display name to use
|
||||
*/
|
||||
public void setDisplayName(IChatBaseComponent displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #uniqueId}
|
||||
* @return uniqueId
|
||||
*/
|
||||
public UUID getUniqueId() {
|
||||
return uniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #uniqueId} to specified value
|
||||
* @param uniqueId
|
||||
* UUID to use
|
||||
*/
|
||||
public void setUniqueId(UUID uniqueId) {
|
||||
this.uniqueId = uniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #name}
|
||||
* @return name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #name} to specified value
|
||||
* @param name
|
||||
* name to use
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #gameMode}
|
||||
* @return gameMode
|
||||
*/
|
||||
public EnumGamemode getGameMode() {
|
||||
return gameMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #gameMode} to specified value
|
||||
* @param gameMode
|
||||
* GameMode to use
|
||||
*/
|
||||
public void setGameMode(EnumGamemode gameMode) {
|
||||
this.gameMode = gameMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #skin}
|
||||
* @return skin
|
||||
*/
|
||||
public Skin getSkin() {
|
||||
return skin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #skin} to specified value
|
||||
* @param skin
|
||||
* Skin to use
|
||||
*/
|
||||
public void setSkin(Skin skin) {
|
||||
this.skin = skin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* En enum representing packet action
|
||||
* Calling ordinal() will return action's network ID.
|
||||
*/
|
||||
public enum EnumPlayerInfoAction {
|
||||
|
||||
ADD_PLAYER,
|
||||
UPDATE_GAME_MODE,
|
||||
UPDATE_LATENCY,
|
||||
UPDATE_DISPLAY_NAME,
|
||||
REMOVE_PLAYER
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum representing GameMode
|
||||
*/
|
||||
public enum EnumGamemode {
|
||||
|
||||
NOT_SET,
|
||||
SURVIVAL,
|
||||
CREATIVE,
|
||||
ADVENTURE,
|
||||
SPECTATOR;
|
||||
|
||||
/** Value array to iterate over to avoid array creations on each call */
|
||||
public static final EnumGamemode[] VALUES = values();
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
|
||||
|
||||
/** TabList header */
|
||||
private final IChatBaseComponent header;
|
||||
|
||||
/** TabList footer */
|
||||
private final IChatBaseComponent footer;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. They are converted to {@link IChatBaseComponent}
|
||||
* using {@link IChatBaseComponent#optimizedComponent(String)} method.
|
||||
*
|
||||
* @param header
|
||||
* TabList header
|
||||
* @param footer
|
||||
* TabList footer
|
||||
*/
|
||||
public PacketPlayOutPlayerListHeaderFooter(String header, String footer) {
|
||||
Preconditions.checkNotNull(header, "header");
|
||||
Preconditions.checkNotNull(footer, "footer");
|
||||
this.header = IChatBaseComponent.optimizedComponent(header);
|
||||
this.footer = IChatBaseComponent.optimizedComponent(footer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param header
|
||||
* TabList header
|
||||
* @param footer
|
||||
* TabList footer
|
||||
*/
|
||||
public PacketPlayOutPlayerListHeaderFooter(IChatBaseComponent header, IChatBaseComponent footer) {
|
||||
Preconditions.checkNotNull(header, "header");
|
||||
Preconditions.checkNotNull(footer, "footer");
|
||||
this.header = header;
|
||||
this.footer = footer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PacketPlayOutPlayerListHeaderFooter{header=%s,footer=%s}", header, footer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #header}
|
||||
* @return header
|
||||
*/
|
||||
public IChatBaseComponent getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #footer}
|
||||
* @return footer
|
||||
*/
|
||||
public IChatBaseComponent getFooter() {
|
||||
return footer;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
|
||||
|
||||
/**
|
||||
* Display slot.
|
||||
* 0 = PlayerList,
|
||||
* 1 = SideBar,
|
||||
* 2 = BelowName.
|
||||
*/
|
||||
private final int slot;
|
||||
|
||||
/** Up to 16 characters long objective name */
|
||||
private final String objectiveName;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param slot
|
||||
* Display slot
|
||||
* @param objectiveName
|
||||
* Internal name of the objective
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code objectiveName} is null or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardDisplayObjective(int slot, String objectiveName) {
|
||||
Preconditions.checkNotNull(objectiveName, "objective name");
|
||||
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
|
||||
this.slot = slot;
|
||||
this.objectiveName = objectiveName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PacketPlayOutScoreboardDisplayObjective{slot=%s,objectiveName=%s}", slot, objectiveName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #slot}
|
||||
* @return slot
|
||||
*/
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #objectiveName}
|
||||
* @return objectiveName
|
||||
*/
|
||||
public String getObjectiveName() {
|
||||
return objectiveName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutScoreboardObjective implements TabPacket {
|
||||
|
||||
/** Up to 16 characters long objective name */
|
||||
private final String objectiveName;
|
||||
|
||||
/** Display name */
|
||||
private final String displayName;
|
||||
|
||||
/** Display type, only takes effect in PlayerList */
|
||||
private final EnumScoreboardHealthDisplay renderType;
|
||||
|
||||
/**
|
||||
* Packet action.
|
||||
* 0 = register,
|
||||
* 1 = unregister,
|
||||
* 2 = update title
|
||||
*/
|
||||
private final int action;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param action
|
||||
* Packet action (0 = add, 1 = remove, 2 = update title)
|
||||
* @param objectiveName
|
||||
* objective name, up to 16 characters long
|
||||
* @param displayName - title
|
||||
* @param renderType - display type
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code objectiveName} is null or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardObjective(int action, String objectiveName, String displayName, EnumScoreboardHealthDisplay renderType) {
|
||||
Preconditions.checkRange(action, 0, 2, "action");
|
||||
Preconditions.checkNotNull(objectiveName, "objective name");
|
||||
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
|
||||
Preconditions.checkNotNull(renderType, "render type");
|
||||
this.objectiveName = objectiveName;
|
||||
this.displayName = displayName;
|
||||
this.renderType = renderType;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new packet with given objective name and 1 (unregister) action.
|
||||
*
|
||||
* @param objectiveName
|
||||
* objective name, up to 16 characters long
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code objectiveName} is null or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardObjective(String objectiveName) {
|
||||
Preconditions.checkNotNull(objectiveName, "objective name");
|
||||
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
|
||||
this.objectiveName = objectiveName;
|
||||
this.displayName = ""; //avoiding NPE on <1.7
|
||||
this.action = 1;
|
||||
this.renderType = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PacketPlayOutScoreboardObjective{objectiveName=%s,displayName=%s,renderType=%s,method=%s}",
|
||||
objectiveName, displayName, renderType, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #displayName}
|
||||
* @return displayName
|
||||
*/
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #objectiveName}
|
||||
* @return objectiveName
|
||||
*/
|
||||
public String getObjectiveName() {
|
||||
return objectiveName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #renderType}
|
||||
* @return renderType
|
||||
*/
|
||||
public EnumScoreboardHealthDisplay getRenderType() {
|
||||
return renderType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #action}
|
||||
* @return packet action
|
||||
*/
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum representing available display types.
|
||||
* They only take effect in PlayerList position.
|
||||
*/
|
||||
public enum EnumScoreboardHealthDisplay {
|
||||
|
||||
INTEGER, HEARTS
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutScoreboardScore implements TabPacket {
|
||||
|
||||
/** Packet action */
|
||||
private final Action action;
|
||||
|
||||
/** Objective name */
|
||||
private final String objectiveName;
|
||||
|
||||
/** Affected player */
|
||||
private final String player;
|
||||
|
||||
/** Player's score */
|
||||
private final int score;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
*
|
||||
* @param action
|
||||
* Packet action
|
||||
* @param objectiveName
|
||||
* Objective name
|
||||
* @param player
|
||||
* Affected player
|
||||
* @param score
|
||||
* Player's score
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code objectiveName} is null or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardScore(Action action, String objectiveName, String player, int score) {
|
||||
Preconditions.checkNotNull(action, "action");
|
||||
Preconditions.checkNotNull(objectiveName, "objective name");
|
||||
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
|
||||
Preconditions.checkNotNull(player, "player");
|
||||
this.action = action;
|
||||
this.objectiveName = objectiveName;
|
||||
this.player = player;
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PacketPlayOutScoreboardScore{action=%s,objectiveName=%s,player=%s,score=%s}",
|
||||
action, objectiveName, player, score);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #action}
|
||||
* @return packet action
|
||||
*/
|
||||
public Action getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #objectiveName}
|
||||
* @return objective name
|
||||
*/
|
||||
public String getObjectiveName() {
|
||||
return objectiveName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #player}
|
||||
* @return player
|
||||
*/
|
||||
public String getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #score}
|
||||
* @return score
|
||||
*/
|
||||
public int getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum representing action types
|
||||
*/
|
||||
public enum Action {
|
||||
|
||||
CHANGE,
|
||||
REMOVE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutScoreboardTeam implements TabPacket {
|
||||
|
||||
/**
|
||||
* Up to 16 characters long team name.
|
||||
* It is used to sort players alphabetically in TabList by team names.
|
||||
* Team-less players appear on top, sorted by usernames.
|
||||
* Players with teams are below them, players of the same team are
|
||||
* sorted alphabetically by usernames.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/** Team prefix, limited to 16 characters, on <1.13 */
|
||||
private String playerPrefix;
|
||||
|
||||
/** Team suffix, limited to 16 characters, on <1.13 */
|
||||
private String playerSuffix;
|
||||
|
||||
/**
|
||||
* NameTag visibility rule since 1.8. Possible options are:
|
||||
* {@code always}, {@code hideForOtherTeams}, {@code hideForOwnTeam}, {@code never}
|
||||
*/
|
||||
private String nameTagVisibility;
|
||||
|
||||
/**
|
||||
* Collision rule added in 1.9. Possible options are:
|
||||
* {@code always}, {@code pushOtherTeams}, {@code pushOwnTeam}, {@code never}
|
||||
*/
|
||||
private String collisionRule;
|
||||
|
||||
/**
|
||||
* Team color field added in 1.13. It determines name color,
|
||||
* prefix and suffix color start.
|
||||
*/
|
||||
private EnumChatFormat color;
|
||||
|
||||
/**
|
||||
* Affected entities. For players, it is their name, for other entities their UUID
|
||||
* Player name length is limited to 16 characters for <1.8, 40 characters since 1.8.
|
||||
* Entity UUID option was added in 1.8.
|
||||
*/
|
||||
private Collection<String> players = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* Packet action.
|
||||
* 0 = create team,
|
||||
* 1 = remove team,
|
||||
* 2 = update team info,
|
||||
* 3 = add entries,
|
||||
* 4 = remove entries
|
||||
*/
|
||||
private final int action;
|
||||
|
||||
/**
|
||||
* Bit mask.
|
||||
* 0x01: Allow friendly fire,
|
||||
* 0x02: Can see invisible players on the same team.
|
||||
*/
|
||||
private int options;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Private constructor used
|
||||
* internally to validate team name and set action.
|
||||
*
|
||||
* @param action
|
||||
* Packet action (0 = create team, 1 = remove team, 2 = update team info,
|
||||
* 3 = add entries, 4 = remove entries)
|
||||
* @param name
|
||||
* Team name, up to 16 characters long
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
private PacketPlayOutScoreboardTeam(int action, String name) {
|
||||
Preconditions.checkNotNull(name, "team name");
|
||||
Preconditions.checkMaxLength(name, 16, "team name");
|
||||
this.action = action;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 0 (CREATE) action
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @param prefix
|
||||
* Prefix of players in team
|
||||
* @param suffix
|
||||
* Suffix of players in team
|
||||
* @param visibility
|
||||
* NameTag visibility rule
|
||||
* @param collision
|
||||
* Collision rule
|
||||
* @param players
|
||||
* Affected entities
|
||||
* @param options
|
||||
* bitmask of team options
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options) {
|
||||
this(0, team);
|
||||
Preconditions.checkNotNull(prefix, "prefix");
|
||||
Preconditions.checkNotNull(suffix, "suffix");
|
||||
Preconditions.checkNotNull(visibility, "visibility");
|
||||
Preconditions.checkNotNull(collision, "collision");
|
||||
Preconditions.checkNotNull(players, "players");
|
||||
this.playerPrefix = prefix;
|
||||
this.playerSuffix = suffix;
|
||||
this.nameTagVisibility = visibility;
|
||||
this.collisionRule = collision;
|
||||
this.players = players;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 1 (REMOVE) action
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team) {
|
||||
this(1, team);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 2 (UPDATE_TEAM_INFO) action
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @param prefix
|
||||
* Prefix of players in team
|
||||
* @param suffix
|
||||
* Suffix of players in team
|
||||
* @param visibility
|
||||
* NameTag visibility rule
|
||||
* @param collision
|
||||
* Collision rule
|
||||
* @param options
|
||||
* bitmask of team options
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, int options) {
|
||||
this(2, team);
|
||||
Preconditions.checkNotNull(prefix, "prefix");
|
||||
Preconditions.checkNotNull(suffix, "suffix");
|
||||
Preconditions.checkNotNull(visibility, "visibility");
|
||||
Preconditions.checkNotNull(collision, "collision");
|
||||
Preconditions.checkNotNull(players, "players");
|
||||
this.playerPrefix = prefix;
|
||||
this.playerSuffix = suffix;
|
||||
this.nameTagVisibility = visibility;
|
||||
this.collisionRule = collision;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 3 (ADD_PLAYERS) if {@code add} is {@code true},
|
||||
* 4 (REMOVE_PLAYERS) if {@code add} is {@code false} action.
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @param players
|
||||
* Affected entities
|
||||
* @param add
|
||||
* {@code true} if players should be added, {@code false} if removed from team
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team, Collection<String> players, boolean add) {
|
||||
this(add ? 3 : 4, team);
|
||||
Preconditions.checkNotNull(players, "players");
|
||||
this.players = players;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PacketPlayOutScoreboardTeam{name=%s,playerPrefix=%s,playerSuffix=%s,nameTagVisibility=%s,"
|
||||
+ "collisionRule=%s,color=%s,players=%s,method=%s,options=%s}",
|
||||
name, playerPrefix, playerSuffix, nameTagVisibility, collisionRule, color, players, action, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #name}
|
||||
* @return team name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #playerPrefix}
|
||||
* @return playerPrefix
|
||||
*/
|
||||
public String getPlayerPrefix() {
|
||||
return playerPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #playerSuffix}
|
||||
* @return playerSuffix
|
||||
*/
|
||||
public String getPlayerSuffix() {
|
||||
return playerSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #nameTagVisibility}
|
||||
* @return nameTagVisibility
|
||||
*/
|
||||
public String getNameTagVisibility() {
|
||||
return nameTagVisibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #collisionRule}
|
||||
* @return collisionRule
|
||||
*/
|
||||
public String getCollisionRule() {
|
||||
return collisionRule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #color} to specified color
|
||||
*
|
||||
* @param color
|
||||
* color to use
|
||||
*/
|
||||
public void setColor(EnumChatFormat color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #color}
|
||||
* @return color
|
||||
*/
|
||||
public EnumChatFormat getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #players}
|
||||
* @return players
|
||||
*/
|
||||
public Collection<String> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #options}
|
||||
* @return team options
|
||||
*/
|
||||
public int getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #action}
|
||||
* @return packet action
|
||||
*/
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* Class representing a minecraft skin as a value - signature pair.
|
||||
*/
|
||||
public class Skin {
|
||||
|
||||
/** Skin value */
|
||||
private final String value;
|
||||
|
||||
/** Skin signature */
|
||||
private final String signature;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param value
|
||||
* skin value
|
||||
* @param signature
|
||||
* skin signature
|
||||
*/
|
||||
public Skin(String value, String signature) {
|
||||
Preconditions.checkNotNull(value, "value");
|
||||
Preconditions.checkNotNull(signature, "signature");
|
||||
this.value = value;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns skin value
|
||||
* @return skin value
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns skin signature
|
||||
* @return skin signature
|
||||
*/
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user-friendly representation of this object
|
||||
* @return user-friendly representation of this object
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Skin{value=%s,signature=%s}", value, signature);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
/**
|
||||
* Interface representing a packet that can be sent to client independently of platform.
|
||||
* It is technically empty, existence is only present to restrict type access
|
||||
* when sending packets to player, so {@link me.neznamy.tab.api.TabPlayer#sendCustomPacket(TabPacket)} cannot
|
||||
* be called with an actual packet.
|
||||
*/
|
||||
public interface TabPacket {
|
||||
|
||||
/**
|
||||
* Returns user-friendly string representation of the packet
|
||||
* @return user-friendly string representation of the packet
|
||||
*/
|
||||
String toString();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
/**
|
||||
* An interface allowing to work with a line of
|
||||
* text in scoreboard.
|
||||
*
|
||||
*/
|
||||
public interface Line {
|
||||
|
||||
/**
|
||||
* Returns configured raw text of this line. Placeholders
|
||||
* remain in raw format.
|
||||
*
|
||||
* @return raw text of this line
|
||||
*/
|
||||
String getText();
|
||||
|
||||
/**
|
||||
* Changes text to new value. Supports placeholders, which are
|
||||
* automatically registered if needed and refreshed periodically
|
||||
* based on configuration. No need to call this method to try to
|
||||
* keep placeholders up to date. If value is identical to previous
|
||||
* one, nothing happens.
|
||||
*
|
||||
* @param text
|
||||
* Raw text to display in the line
|
||||
*/
|
||||
void setText(String text);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An interface allowing to work with scoreboard, such as adding players,
|
||||
* removing players and changing lines. New instance can be created using
|
||||
* {@link ScoreboardManager#createScoreboard(String, String, List)},
|
||||
* for scoreboards from config {@link ScoreboardManager#getRegisteredScoreboards()}.
|
||||
*/
|
||||
public interface Scoreboard {
|
||||
|
||||
/**
|
||||
* Returns internal name of this scoreboard. Name is defined in registration,
|
||||
* scoreboards from config use name they were defined with. This value is
|
||||
* used internally and in /tab announce scoreboard command.
|
||||
*
|
||||
* @return custom name of this scoreboard
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns raw title of this scoreboard. Placeholders stay in their
|
||||
* raw format.
|
||||
*
|
||||
* @return scoreboard title
|
||||
* @see #setTitle(String)
|
||||
*/
|
||||
String getTitle();
|
||||
|
||||
/**
|
||||
* Sets title to specified value. Placeholders are refreshed automatically
|
||||
* with refresh intervals defined in config. No need to call this method
|
||||
* to try to keep placeholders up to date. Supports RGB codes using any of
|
||||
* the supported formats.
|
||||
* <p>
|
||||
* Length is limited to 32 characters on <1.13. If the limit is exceeded,
|
||||
* text will be cut to 32 characters.
|
||||
* <p>
|
||||
* Calling this method with same title as before will not do anything.
|
||||
*
|
||||
* @param title
|
||||
* New title to use with placeholder support
|
||||
* @see #getTitle()
|
||||
*/
|
||||
void setTitle(String title);
|
||||
|
||||
/**
|
||||
* Returns list of lines in this scoreboard in the order they appear
|
||||
* in game (first line is on top). This list should only be used for reading,
|
||||
* for adding/removing lines see {@link #addLine(String)} and {@link #removeLine(int)}.
|
||||
*
|
||||
* @return list of lines in this scoreboard
|
||||
* @see #addLine(String)
|
||||
* @see #removeLine(int)
|
||||
*/
|
||||
List<Line> getLines();
|
||||
|
||||
/**
|
||||
* Adds line with specified text on the bottom of scoreboard. Supports
|
||||
* placeholders, which will automatically be refreshed.
|
||||
*
|
||||
* @param text
|
||||
* Text to display
|
||||
* @see #getLines()
|
||||
* @see #removeLine(int)
|
||||
*/
|
||||
void addLine(String text);
|
||||
|
||||
/**
|
||||
* Removes line with specified index. Index starts at {@code 0} and ends at
|
||||
* {@link #getLines()}.size()-1.
|
||||
*
|
||||
* @param index
|
||||
* Index of line to remove, starting at 0
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the index is out of range (index < 0 || index >= {@link #getLines()}.size())
|
||||
* @see #getLines()
|
||||
* @see #addLine(String)
|
||||
*/
|
||||
void removeLine(int index);
|
||||
|
||||
/**
|
||||
* Unregisters this scoreboard from all players who can see it.
|
||||
*/
|
||||
void unregister();
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface allowing work with scoreboards, such as creating,
|
||||
* sending, toggling and modifying.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link me.neznamy.tab.api.TabAPI#getScoreboardManager()}.
|
||||
* This requires the scoreboard feature to be enabled in config,
|
||||
* the method will return null otherwise.
|
||||
*/
|
||||
public interface ScoreboardManager {
|
||||
|
||||
/**
|
||||
* Creates new scoreboard, registers and returns it. {@code name}
|
||||
* is internal name of the scoreboard, which it will be registered
|
||||
* with and further work where scoreboard name is used allowed.
|
||||
* <p>
|
||||
* Scoreboard {@code title} is limited to 32 characters on <1.13. If needed,
|
||||
* it will be cut to 32 character for those players (after replacing
|
||||
* placeholders). RGB is supported using any of the supported formats.
|
||||
* Placeholders are supported.
|
||||
* <p>
|
||||
* {@code lines} will display in the same order as contained in the list.
|
||||
* Client can only see up to 15 lines at a time, however you can define
|
||||
* more, if some of them only consist of placeholder that might be empty.
|
||||
*
|
||||
* @param name
|
||||
* Internal name of the scoreboard
|
||||
* @param title
|
||||
* The scoreboard title
|
||||
* @param lines
|
||||
* Lines of text in scoreboard (supports placeholders)
|
||||
* @return The new scoreboard with given parameters
|
||||
* @throws NullPointerException
|
||||
* if {@code name} or {@code lines} is {@code null}
|
||||
*/
|
||||
Scoreboard createScoreboard(String name, String title, List<String> lines);
|
||||
|
||||
/**
|
||||
* Returns map of registered scoreboards via config and API.
|
||||
* Map key is name of scoreboard, which is defined either in config
|
||||
* or using {@link #createScoreboard(String, String, List)}.
|
||||
* Map value is reference to the actual scoreboard.
|
||||
*
|
||||
* @return map of registered scoreboards
|
||||
*/
|
||||
Map<String, Scoreboard> getRegisteredScoreboards();
|
||||
|
||||
/**
|
||||
* Displays scoreboard to defined player. This will disable all display
|
||||
* condition checks until the scoreboard is unregistered using
|
||||
* {@link #resetScoreboard(TabPlayer)}. If the player already sees
|
||||
* this scoreboard, nothing will happen.
|
||||
*
|
||||
* @param player
|
||||
* Player to send scoreboard to
|
||||
* @param scoreboard
|
||||
* Scoreboard to display
|
||||
* @throws NullPointerException
|
||||
* if {@code player} or {@code scoreboard} is {@code null}
|
||||
* @see #hasCustomScoreboard(TabPlayer)
|
||||
* @see #resetScoreboard(TabPlayer)
|
||||
*/
|
||||
void showScoreboard(TabPlayer player, Scoreboard scoreboard);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has custom scoreboard set using
|
||||
* {@link #showScoreboard(TabPlayer, Scoreboard)}, {@code false} if not.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if player has custom scoreboard set using API,
|
||||
* {@code false} if not
|
||||
* @see #showScoreboard(TabPlayer, Scoreboard)
|
||||
* @see #resetScoreboard(TabPlayer)
|
||||
*/
|
||||
boolean hasCustomScoreboard(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Hides custom scoreboard sent using {@link #showScoreboard(TabPlayer, Scoreboard)}
|
||||
* and re-enables internal display logic with conditions. If player does not
|
||||
* have any forced scoreboard, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* Player to hide custom scoreboard from
|
||||
* @see #showScoreboard(TabPlayer, Scoreboard)
|
||||
* @see #hasCustomScoreboard(TabPlayer)
|
||||
*/
|
||||
void resetScoreboard(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has scoreboard enabled, {@code false}
|
||||
* if disabled (toggled) using either toggle command,
|
||||
* {@link #toggleScoreboard(TabPlayer, boolean)} or
|
||||
* {@link #setScoreboardVisible(TabPlayer, boolean, boolean)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get visibility status of
|
||||
* @return {@code true} if visible, {@code false} if disabled
|
||||
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
|
||||
* @see #toggleScoreboard(TabPlayer, boolean)
|
||||
*/
|
||||
boolean hasScoreboardVisible(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sets scoreboard visibility of player to defined value. If visibility status
|
||||
* is same as player had before, nothing happens. If this call changes
|
||||
* visibility, scoreboard is toggled. If {@code sendToggleMessage} is {@code true},
|
||||
* toggle message defined in configuration is sent.
|
||||
*
|
||||
* @param player
|
||||
* Player to set visibility for
|
||||
* @param visible
|
||||
* New visibility status
|
||||
* @param sendToggleMessage
|
||||
* whether to send toggle message if status changed or not
|
||||
* @see #toggleScoreboard(TabPlayer, boolean)
|
||||
* @see #hasScoreboardVisible(TabPlayer)
|
||||
*/
|
||||
void setScoreboardVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Toggles scoreboard for specified player. If player had scoreboard visible,
|
||||
* hides it. If hidden, shows it. If {@code sendToggleMessage} is {@code true},
|
||||
* toggle message defined in configuration is sent.
|
||||
*
|
||||
* @param player
|
||||
* Player to toggle scoreboard for
|
||||
* @param sendToggleMessage
|
||||
* Whether to send toggle message or not
|
||||
* @see #hasScoreboardVisible(TabPlayer)
|
||||
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
void toggleScoreboard(TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Temporarily displays scoreboard to all players for specified amount of
|
||||
* time (in milliseconds). Scoreboard name is either defined in config, or
|
||||
* through API in {@link #createScoreboard(String, String, List)}.
|
||||
*
|
||||
* @param scoreboard
|
||||
* Scoreboard from config or registered via API
|
||||
* @param duration
|
||||
* Duration in milliseconds
|
||||
* @throws IllegalArgumentException
|
||||
* if no scoreboard was found with such name or {@code duration}
|
||||
* is < 0.
|
||||
*/
|
||||
void announceScoreboard(String scoreboard, int duration);
|
||||
|
||||
/**
|
||||
* Returns player's currently displayed scoreboard. This can be either with
|
||||
* configuration, overridden with commands or the API. Will return {@code null}
|
||||
* if player does not see any scoreboard due to not meeting any display condition.
|
||||
* @param player
|
||||
* player to get active scoreboard of
|
||||
* @return player's active scoreboard or {@code null} if player has no scoreboard
|
||||
*/
|
||||
Scoreboard getActiveScoreboard(TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package me.neznamy.tab.api.task;
|
||||
|
||||
public interface RepeatingTask {
|
||||
|
||||
int getInterval();
|
||||
|
||||
void setInterval(int paramInt);
|
||||
|
||||
void cancel();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package me.neznamy.tab.api.task;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
|
||||
public interface ThreadManager {
|
||||
|
||||
/**
|
||||
* Starts a task in new thread and measures how long it took to process
|
||||
* @param feature - feature to add cpu usage to
|
||||
* @param type - usage type to add cpu usage to
|
||||
* @param task - the task
|
||||
*/
|
||||
Future<Void> runMeasuredTask(TabFeature feature, String type, Runnable task);
|
||||
|
||||
/**
|
||||
* Starts a task in new thread and measures how long it took to process
|
||||
* @param feature - feature to add cpu usage to
|
||||
* @param type - usage type to add cpu usage to
|
||||
* @param task - the task
|
||||
*/
|
||||
Future<Void> runMeasuredTask(String feature, String type, Runnable task);
|
||||
|
||||
/**
|
||||
* Runs task in a new thread
|
||||
* @param task - the task
|
||||
*/
|
||||
Future<Void> runTask(Runnable task);
|
||||
|
||||
/**
|
||||
* Starts a new task with defined repeat interval that measures cpu usage
|
||||
* @param intervalMilliseconds - task interval
|
||||
* @param feature - feature to add cpu usage to
|
||||
* @param type - usage type to add cpu usage to
|
||||
* @param task - the task
|
||||
*/
|
||||
RepeatingTask startRepeatingMeasuredTask(int intervalMilliseconds, TabFeature feature, String type, Runnable task);
|
||||
|
||||
RepeatingTask startRepeatingTask(int intervalMilliseconds, Runnable task);
|
||||
|
||||
/**
|
||||
* Runs task with a delay and measures how long it took to process
|
||||
* @param delayMilliseconds - how long to run the task after
|
||||
* @param feature - feature to add cpu usage to
|
||||
* @param type - usage type to add cpu usage to
|
||||
* @param task - the task
|
||||
* @return future allowing to cancel the task
|
||||
*/
|
||||
Future<?> runTaskLater(int delayMilliseconds, TabFeature feature, String type, Runnable task);
|
||||
|
||||
Future<?> runTaskLater(int delayMilliseconds, Runnable task);
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package me.neznamy.tab.api.team;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
public interface TeamManager {
|
||||
|
||||
/**
|
||||
* Makes player's NameTag globally invisible
|
||||
* @see #showNametag(TabPlayer)
|
||||
* @see #hasHiddenNametag(TabPlayer)
|
||||
*/
|
||||
void hideNametag(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Hides player's NameTag for specified player until it's shown again
|
||||
* @param viewer - player to hide NameTag for
|
||||
*/
|
||||
void hideNametag(TabPlayer player, TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Makes player's NameTag visible again
|
||||
* @see #hideNametag(TabPlayer)
|
||||
* @see #hasHiddenNametag(TabPlayer)
|
||||
*/
|
||||
void showNametag(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Shows player's NameTag for specified viewer if it was hidden before
|
||||
* @param viewer - player to show NameTag back for
|
||||
*/
|
||||
void showNametag(TabPlayer player, TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Return whether player has hidden NameTag or not
|
||||
* @return Whether player has hidden NameTag or not
|
||||
* @see #hideNametag(TabPlayer)
|
||||
* @see #showNametag(TabPlayer)
|
||||
*/
|
||||
boolean hasHiddenNametag(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns true if NameTag is hidden for specified viewer, false if not
|
||||
* @param viewer - player to check visibility status for
|
||||
* @return true if hidden, false if not
|
||||
*/
|
||||
boolean hasHiddenNametag(TabPlayer player, TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
|
||||
* This can be resumed using resumeTeamHandling(). If team handling was paused already, nothing happens.
|
||||
*/
|
||||
void pauseTeamHandling(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
|
||||
*/
|
||||
void resumeTeamHandling(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
|
||||
* it was resumed already using resumeTeamHandling
|
||||
* @return true if paused, false if not
|
||||
*/
|
||||
boolean hasTeamHandlingPaused(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Forces new team name for the player until this method is called again with null argument and
|
||||
* performs all actions to change player's team name
|
||||
* @param teamName - forced team name
|
||||
*/
|
||||
void forceTeamName(TabPlayer player, String teamName);
|
||||
|
||||
/**
|
||||
* Returns forced team name of player or null if not forced
|
||||
* @return forced team name of player or null if not forced
|
||||
*/
|
||||
String getForcedTeamName(TabPlayer player);
|
||||
|
||||
|
||||
/**
|
||||
* Forces collision rule for the player. Setting it to null will remove forced value
|
||||
* @param collision - forced collision rule
|
||||
*/
|
||||
void setCollisionRule(TabPlayer player, Boolean collision);
|
||||
|
||||
/**
|
||||
* Returns forced collision rule or null if collision is not forced using setCollisionRule
|
||||
* @return forced value or null if not forced
|
||||
*/
|
||||
Boolean getCollisionRule(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sends update team's properties packet of player's team to everyone
|
||||
* @param player - player to update team data of
|
||||
*/
|
||||
void updateTeamData(TabPlayer player);
|
||||
|
||||
void setPrefix(TabPlayer player, String prefix);
|
||||
|
||||
void setSuffix(TabPlayer player, String suffix);
|
||||
|
||||
void resetPrefix(TabPlayer player);
|
||||
|
||||
void resetSuffix(TabPlayer player);
|
||||
|
||||
String getCustomPrefix(TabPlayer player);
|
||||
|
||||
String getCustomSuffix(TabPlayer player);
|
||||
|
||||
String getOriginalPrefix(TabPlayer player);
|
||||
|
||||
String getOriginalSuffix(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Toggles nametag visibility view on all players for specified player.
|
||||
* On first call, nametags of all players will become invisible for specified
|
||||
* player. On second call, they will become visible again.
|
||||
*
|
||||
* @param player
|
||||
* player to toggle nametag visibility view for
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if configured toggle message should be sent, {@code false} if not
|
||||
*/
|
||||
void toggleNameTagVisibilityView(TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has hidden nametags by either calling
|
||||
* {@link #toggleNameTagVisibilityView(TabPlayer, boolean)} or using a command,
|
||||
* {@code false} if not.
|
||||
*
|
||||
* @param player
|
||||
* player to check
|
||||
* @return {@code true} if hidden, {@code false} if not
|
||||
*/
|
||||
boolean hasHiddenNameTagVisibilityView(TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package me.neznamy.tab.api.team;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
public interface UnlimitedNametagManager extends TeamManager {
|
||||
|
||||
void disableArmorStands(TabPlayer player);
|
||||
|
||||
void enableArmorStands(TabPlayer player);
|
||||
|
||||
boolean hasDisabledArmorStands(TabPlayer player);
|
||||
|
||||
void setName(TabPlayer player, String customName);
|
||||
|
||||
void setLine(TabPlayer player, String line, String value);
|
||||
|
||||
void resetName(TabPlayer player);
|
||||
|
||||
void resetLine(TabPlayer player, String line);
|
||||
|
||||
String getCustomName(TabPlayer player);
|
||||
|
||||
String getCustomLineValue(TabPlayer player, String line);
|
||||
|
||||
String getOriginalName(TabPlayer player);
|
||||
|
||||
String getOriginalLineValue(TabPlayer player, String line);
|
||||
|
||||
List<String> getDefinedLines();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.neznamy.tab.api.util;
|
||||
|
||||
/**
|
||||
* An interface for BiFunction which can throw an exception.
|
||||
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
|
||||
* call build method for packet classes, which may throw an
|
||||
* exception due to reflection.
|
||||
* @param <A>
|
||||
* First function argument
|
||||
* @param <B>
|
||||
* Second function argument
|
||||
* @param <C>
|
||||
* Function return type
|
||||
*/
|
||||
public interface BiFunctionWithException<A, B, C> {
|
||||
|
||||
C apply(A a, B b) throws ReflectiveOperationException;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package me.neznamy.tab.api.util;
|
||||
|
||||
public class Preconditions {
|
||||
|
||||
private Preconditions(){}
|
||||
|
||||
public static void checkNotNull(Object obj, String name){
|
||||
if (obj == null) throw new IllegalArgumentException(name + " cannot be null");
|
||||
}
|
||||
|
||||
public static void checkMaxLength(String string, int maxLength, String name) {
|
||||
checkNotNull(string, name);
|
||||
if (string.length() > maxLength) throw new IllegalArgumentException(name + " is longer than " + maxLength + " characters (" + string.length() + ")");
|
||||
}
|
||||
|
||||
public static void checkRange(Number number, Number min, Number max, String variable){
|
||||
if (number.doubleValue() < min.doubleValue() || number.doubleValue() > max.doubleValue())
|
||||
throw new IllegalArgumentException(variable + " index out of range (" + min + " - " + max + ")");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.0")
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
filter<ReplaceTokens>("tokens" to mapOf(
|
||||
"name" to rootProject.name,
|
||||
"version" to project.version,
|
||||
"description" to project.description
|
||||
))
|
||||
}
|
||||
javadoc {
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
|
||||
}
|
||||
compileJava {
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
options.release.set(8)
|
||||
options.compilerArgs.addAll(listOf("-nowarn", "-Xlint:-unchecked", "-Xlint:-deprecation"))
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(17))
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
@@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
id("tab.standard-conventions")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
}
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
archiveFileName.set("TAB-${project.name}-${project.version}.jar")
|
||||
relocate("org.bstats", "me.neznamy.tab.libs.org.bstats")
|
||||
relocate("org.json.simple", "me.neznamy.tab.libs.org.json.simple")
|
||||
relocate("net.kyori.event", "me.neznamy.tab.libs.net.kyori.event")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import gradle.kotlin.dsl.accessors._4cbb0020bd75d48acc42cabded5fdbb7.publishing
|
||||
import org.gradle.api.artifacts.repositories.PasswordCredentials
|
||||
import org.gradle.api.publish.maven.MavenPublication
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.kotlin.dsl.repositories
|
||||
|
||||
plugins {
|
||||
id("tab.base-conventions")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "krypton"
|
||||
url = uri("https://repo.kryptonmc.org/releases")
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
}
|
||||
publications.create<MavenPublication>("mavenJava") {
|
||||
groupId = rootProject.group as String
|
||||
artifactId = "tab-${project.name}"
|
||||
version = rootProject.version as String
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
id("tab.parent")
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = property("projectVersion") as String // from gradle.properties
|
||||
description = "An all-in-one solution that works"
|
||||
}
|
||||
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bungeecord,
|
||||
// projects.krypton,
|
||||
projects.velocity
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
projects.api,
|
||||
projects.shared
|
||||
).map { it.dependencyProject }
|
||||
|
||||
subprojects {
|
||||
when (this) {
|
||||
in platforms -> plugins.apply("tab.platform-conventions")
|
||||
in special -> plugins.apply("tab.standard-conventions")
|
||||
else -> plugins.apply("tab.base-conventions")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation(libs.bstats.bukkit)
|
||||
compileOnly(libs.bukkit)
|
||||
compileOnly(libs.placeholderapi)
|
||||
compileOnly(libs.vault)
|
||||
compileOnly(libs.via)
|
||||
compileOnly(libs.authlib)
|
||||
compileOnly(libs.libsDisguises)
|
||||
compileOnly(libs.essentials)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
|
||||
/**
|
||||
* The core for bukkit forwarding events into all enabled features
|
||||
*/
|
||||
public class BukkitEventListener implements Listener {
|
||||
|
||||
private final BukkitPlatform platform;
|
||||
|
||||
public BukkitEventListener(BukkitPlatform platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerQuitEvent to remove player data and forward the event to features
|
||||
* @param e quit event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e){
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onQuit(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerJoinEvent to create player data and forward the event to features
|
||||
* @param e join event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerChangedWorldEvent to forward the event to features
|
||||
* @param e world changed event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e){
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerChangedWorldEvent to forward the event to features
|
||||
* @param e command preprocess event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onCommand(PlayerCommandPreprocessEvent e) {
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
if (TAB.getInstance().getFeatureManager().onCommand(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import me.neznamy.tab.api.protocol.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.chat.ChatClickable.EnumClickAction;
|
||||
import me.neznamy.tab.api.chat.ChatComponentEntity;
|
||||
import me.neznamy.tab.api.chat.ChatHoverable.EnumHoverAction;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.chat.TextColor;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumGamemode;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class BukkitPacketBuilder extends PacketBuilder {
|
||||
|
||||
//nms storage
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
|
||||
//entity type ids
|
||||
private final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
|
||||
|
||||
private final Map<IChatBaseComponent, Object> componentCacheModern = new HashMap<>();
|
||||
private final Map<IChatBaseComponent, Object> componentCacheLegacy = new HashMap<>();
|
||||
|
||||
private Object emptyScoreboard;
|
||||
private Object dummyEntity;
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BukkitPacketBuilder() {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 1);
|
||||
entityIds.put(EntityType.WITHER, 83);
|
||||
} else {
|
||||
entityIds.put(EntityType.WITHER, 64);
|
||||
if (nms.getMinorVersion() >= 8){
|
||||
entityIds.put(EntityType.ARMOR_STAND, 30);
|
||||
}
|
||||
}
|
||||
try {
|
||||
emptyScoreboard = nms.newScoreboard.newInstance();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Failed to create instance of \"Scoreboard\""));
|
||||
}
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
try {
|
||||
dummyEntity = nms.newEntityArmorStand.newInstance(nms.World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Failed to create instance of \"EntityArmorStand\""));
|
||||
}
|
||||
}
|
||||
buildMap.put(PacketPlayOutEntityMetadata.class, (packet, version) -> build((PacketPlayOutEntityMetadata)packet));
|
||||
buildMap.put(PacketPlayOutEntityTeleport.class, (packet, version) -> build((PacketPlayOutEntityTeleport)packet));
|
||||
buildMap.put(PacketPlayOutEntityDestroy.class, (packet, version) -> build((PacketPlayOutEntityDestroy)packet));
|
||||
buildMap.put(PacketPlayOutSpawnEntityLiving.class, (packet, version) -> build((PacketPlayOutSpawnEntityLiving)packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() >= 9 || clientVersion.getMinorVersion() >= 9) {
|
||||
//1.9+ server or client, handled by bukkit api or ViaVersion
|
||||
return packet;
|
||||
}
|
||||
//<1.9 client and server
|
||||
return buildBossPacketEntity(packet, clientVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
Object component = toNMSComponent(packet.getMessage(), clientVersion);
|
||||
if (nms.getMinorVersion() >= 16) {
|
||||
return nms.newPacketPlayOutChat.newInstance(component, nms.ChatMessageType_values[packet.getType().ordinal()], UUID.randomUUID());
|
||||
}
|
||||
if (nms.getMinorVersion() >= 12) {
|
||||
return nms.newPacketPlayOutChat.newInstance(component, Enum.valueOf((Class<Enum>) nms.ChatMessageType, packet.getType().toString()));
|
||||
}
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
return nms.newPacketPlayOutChat.newInstance(component, (byte) packet.getType().ordinal());
|
||||
}
|
||||
if (nms.getMinorVersion() == 7) {
|
||||
return nms.newPacketPlayOutChat.newInstance(component);
|
||||
}
|
||||
throw new IllegalStateException("Not supported on <1.7");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
Object nmsPacket = nms.newPacketPlayOutPlayerInfo.newInstance(nms.EnumPlayerInfoAction_values[packet.getAction().ordinal()], Array.newInstance(nms.EntityPlayer, 0));
|
||||
List<Object> items = new ArrayList<>();
|
||||
for (PlayerInfoData data : packet.getEntries()) {
|
||||
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
|
||||
if (data.getSkin() != null) profile.getProperties().put("textures", new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (nms.newPlayerInfoData.getParameterCount() == 5) {
|
||||
parameters.add(nmsPacket);
|
||||
}
|
||||
parameters.add(profile);
|
||||
parameters.add(data.getLatency());
|
||||
parameters.add(data.getGameMode() == null ? null : nms.EnumGamemode_values[nms.EnumGamemode_values.length-EnumGamemode.VALUES.length+data.getGameMode().ordinal()]); //not_set was removed in 1.17
|
||||
parameters.add(data.getDisplayName() == null ? null : toNMSComponent(data.getDisplayName(), clientVersion));
|
||||
items.add(nms.newPlayerInfoData.newInstance(parameters.toArray()));
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_PLAYERS, items);
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
if (nms.newPacketPlayOutPlayerListHeaderFooter.getParameterCount() == 2) {
|
||||
return nms.newPacketPlayOutPlayerListHeaderFooter.newInstance(toNMSComponent(packet.getHeader(), clientVersion), toNMSComponent(packet.getFooter(), clientVersion));
|
||||
}
|
||||
Object nmsPacket = nms.newPacketPlayOutPlayerListHeaderFooter.newInstance();
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutPlayerListHeaderFooter_HEADER, toNMSComponent(packet.getHeader(), clientVersion));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutPlayerListHeaderFooter_FOOTER, toNMSComponent(packet.getFooter(), clientVersion));
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return nms.newPacketPlayOutScoreboardDisplayObjective.newInstance(packet.getSlot(), newScoreboardObjective(packet.getObjectiveName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
String displayName = clientVersion.getMinorVersion() < 13 ? cutTo(packet.getDisplayName(), 32) : packet.getDisplayName();
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return nms.newPacketPlayOutScoreboardObjective.newInstance(nms.newScoreboardObjective.newInstance(null, packet.getObjectiveName(), null,
|
||||
toNMSComponent(IChatBaseComponent.optimizedComponent(displayName), clientVersion),
|
||||
packet.getRenderType() == null ? null : nms.EnumScoreboardHealthDisplay_values[packet.getRenderType().ordinal()]),
|
||||
packet.getAction());
|
||||
}
|
||||
|
||||
Object nmsPacket = nms.newPacketPlayOutScoreboardObjective.newInstance();
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME, packet.getObjectiveName());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_DISPLAYNAME, displayName);
|
||||
if (nms.getMinorVersion() >= 8 && packet.getRenderType() != null) {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_RENDERTYPE, Enum.valueOf((Class<Enum>) nms.EnumScoreboardHealthDisplay, packet.getRenderType().toString()));
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_METHOD, packet.getAction());
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return nms.newPacketPlayOutScoreboardScore_1_13.newInstance(nms.EnumScoreboardAction_values[packet.getAction().ordinal()], packet.getObjectiveName(), packet.getPlayer(), packet.getScore());
|
||||
}
|
||||
if (packet.getAction() == PacketPlayOutScoreboardScore.Action.REMOVE) {
|
||||
return nms.newPacketPlayOutScoreboardScore_String.newInstance(packet.getPlayer());
|
||||
}
|
||||
Object score = nms.newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(packet.getObjectiveName()), packet.getPlayer());
|
||||
nms.ScoreboardScore_setScore.invoke(score, packet.getScore());
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
return nms.newPacketPlayOutScoreboardScore.newInstance(score);
|
||||
}
|
||||
return nms.newPacketPlayOutScoreboardScore.newInstance(score, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.PacketPlayOutScoreboardTeam == null) return null; //fabric
|
||||
Object team = nms.newScoreboardTeam.newInstance(emptyScoreboard, packet.getName());
|
||||
String prefix = packet.getPlayerPrefix();
|
||||
String suffix = packet.getPlayerSuffix();
|
||||
if (clientVersion.getMinorVersion() < 13) {
|
||||
prefix = cutTo(prefix, 16);
|
||||
suffix = cutTo(suffix, 16);
|
||||
}
|
||||
((Collection<String>)nms.ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(packet.getPlayers());
|
||||
nms.ScoreboardTeam_setAllowFriendlyFire.invoke(team, (packet.getOptions() & 0x1) > 0);
|
||||
nms.ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (packet.getOptions() & 0x2) > 0);
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
createTeamModern(packet, clientVersion, team, prefix, suffix);
|
||||
} else {
|
||||
createTeamLegacy(packet, team, prefix, suffix);
|
||||
}
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
switch (packet.getAction()) {
|
||||
case 0:
|
||||
return nms.PacketPlayOutScoreboardTeam_ofBoolean.invoke(null, team, true);
|
||||
case 1:
|
||||
return nms.PacketPlayOutScoreboardTeam_of.invoke(null, team);
|
||||
case 2:
|
||||
return nms.PacketPlayOutScoreboardTeam_ofBoolean.invoke(null, team, false);
|
||||
case 3:
|
||||
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.PacketPlayOutScoreboardTeam_PlayerAction_values[0]);
|
||||
case 4:
|
||||
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.PacketPlayOutScoreboardTeam_PlayerAction_values[1]);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid action: " + packet.getAction());
|
||||
}
|
||||
}
|
||||
return nms.newPacketPlayOutScoreboardTeam.newInstance(team, packet.getAction());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity destroy packet with given parameter
|
||||
* @return destroy packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutEntityDestroy packet) throws ReflectiveOperationException {
|
||||
try {
|
||||
return nms.newPacketPlayOutEntityDestroy.newInstance(new Object[]{packet.getEntities()});
|
||||
} catch (IllegalArgumentException e) {
|
||||
//1.17.0
|
||||
return nms.newPacketPlayOutEntityDestroy.newInstance(packet.getEntities()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public Object build(PacketPlayOutEntityMetadata packet) throws ReflectiveOperationException {
|
||||
return nms.newPacketPlayOutEntityMetadata.newInstance(packet.getEntityId(), packet.getDataWatcher().toNMS(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity spawn packet with given parameters
|
||||
*
|
||||
* @return entity spawn packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutSpawnEntityLiving packet) throws ReflectiveOperationException {
|
||||
Object nmsPacket;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
nmsPacket = nms.newPacketPlayOutSpawnEntityLiving.newInstance(dummyEntity);
|
||||
} else {
|
||||
nmsPacket = nms.newPacketPlayOutSpawnEntityLiving.newInstance();
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYID, packet.getEntityId());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYTYPE, entityIds.get(packet.getEntityType()));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_YAW, (byte)(packet.getLocation().getYaw() * 256.0f / 360.0f));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_PITCH, (byte)(packet.getLocation().getPitch() * 256.0f / 360.0f));
|
||||
if (nms.getMinorVersion() <= 14) {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER, packet.getDataWatcher().toNMS());
|
||||
}
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_UUID, packet.getUniqueId());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_X, packet.getLocation().getX());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Y, packet.getLocation().getY());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Z, packet.getLocation().getZ());
|
||||
} else {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_X, floor(packet.getLocation().getX()*32));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Y, floor(packet.getLocation().getY()*32));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Z, floor(packet.getLocation().getZ()*32));
|
||||
}
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity teleport packet with given parameters
|
||||
* @return entity teleport packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutEntityTeleport packet) throws ReflectiveOperationException {
|
||||
Object nmsPacket;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
nmsPacket = nms.newPacketPlayOutEntityTeleport.newInstance(dummyEntity);
|
||||
} else {
|
||||
nmsPacket = nms.newPacketPlayOutEntityTeleport.newInstance();
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_ENTITYID, packet.getEntityId());
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_X, packet.getLocation().getX());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Y, packet.getLocation().getY());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Z, packet.getLocation().getZ());
|
||||
} else {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_X, floor(packet.getLocation().getX()*32));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Y, floor(packet.getLocation().getY()*32));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Z, floor(packet.getLocation().getZ()*32));
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_YAW, (byte) (packet.getLocation().getYaw()/360*256));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_PITCH, (byte) (packet.getLocation().getPitch()/360*256));
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
private void createTeamModern(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion, Object team, String prefix, String suffix) throws ReflectiveOperationException {
|
||||
if (prefix != null) nms.ScoreboardTeam_setPrefix.invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
|
||||
if (suffix != null) nms.ScoreboardTeam_setSuffix.invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
|
||||
EnumChatFormat format = packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(prefix);
|
||||
nms.ScoreboardTeam_setColor.invoke(team, nms.EnumChatFormat_values[format.ordinal()]);
|
||||
nms.ScoreboardTeam_setNameTagVisibility.invoke(team, String.valueOf(packet.getNameTagVisibility()).equals("always") ? nms.EnumNameTagVisibility_values[0] : nms.EnumNameTagVisibility_values[1]);
|
||||
nms.ScoreboardTeam_setCollisionRule.invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.EnumTeamPush_values[0] : nms.EnumTeamPush_values[1]);
|
||||
}
|
||||
|
||||
private void createTeamLegacy(PacketPlayOutScoreboardTeam packet, Object team, String prefix, String suffix) throws ReflectiveOperationException {
|
||||
if (prefix != null) nms.ScoreboardTeam_setPrefix.invoke(team, prefix);
|
||||
if (suffix != null) nms.ScoreboardTeam_setSuffix.invoke(team, suffix);
|
||||
if (nms.getMinorVersion() >= 8) nms.ScoreboardTeam_setNameTagVisibility.invoke(team, String.valueOf(packet.getNameTagVisibility()).equals("always") ? nms.EnumNameTagVisibility_values[0] : nms.EnumNameTagVisibility_values[1]);
|
||||
if (nms.getMinorVersion() >= 9) nms.ScoreboardTeam_setCollisionRule.invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.EnumTeamPush_values[0] : nms.EnumTeamPush_values[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
EnumPlayerInfoAction action = EnumPlayerInfoAction.valueOf(nms.PacketPlayOutPlayerInfo_ACTION.get(nmsPacket).toString());
|
||||
List<PlayerInfoData> listData = new ArrayList<>();
|
||||
for (Object nmsData : (List<?>) nms.PacketPlayOutPlayerInfo_PLAYERS.get(nmsPacket)) {
|
||||
Object nmsGameMode = nms.PlayerInfoData_getGamemode.invoke(nmsData);
|
||||
EnumGamemode gameMode = (nmsGameMode == null) ? null : EnumGamemode.valueOf(nmsGameMode.toString());
|
||||
GameProfile profile = (GameProfile) nms.PlayerInfoData_getProfile.invoke(nmsData);
|
||||
Object nmsComponent = nms.PlayerInfoData_getDisplayName.invoke(nmsData);
|
||||
IChatBaseComponent listName = nmsComponent == null ? null : fromNMSComponent(nmsComponent);
|
||||
Skin skin = null;
|
||||
if (!profile.getProperties().get("textures").isEmpty()) {
|
||||
Property pr = (Property) profile.getProperties().get("textures").iterator().next();
|
||||
skin = new Skin(pr.getValue(), pr.getSignature());
|
||||
}
|
||||
listData.add(new PlayerInfoData(profile.getName(), profile.getId(), skin,
|
||||
(int) nms.PlayerInfoData_getLatency.invoke(nmsData), gameMode, listName));
|
||||
}
|
||||
return new PacketPlayOutPlayerInfo(action, listData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket) throws ReflectiveOperationException {
|
||||
return new PacketPlayOutScoreboardObjective(nms.PacketPlayOutScoreboardObjective_METHOD.getInt(nmsPacket),
|
||||
(String) nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME.get(nmsPacket), null,
|
||||
PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay.INTEGER
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket) throws ReflectiveOperationException {
|
||||
return new PacketPlayOutScoreboardDisplayObjective(
|
||||
nms.PacketPlayOutScoreboardDisplayObjective_POSITION.getInt(nmsPacket),
|
||||
(String) nms.PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME.get(nmsPacket)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity BossBar packet
|
||||
* @param packet - packet to build
|
||||
* @param clientVersion - client version
|
||||
* @return entity BossBar packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (packet.getAction() == Action.UPDATE_STYLE) return null; //nothing to do here
|
||||
|
||||
int entityId = packet.getId().hashCode();
|
||||
if (packet.getAction() == Action.REMOVE) {
|
||||
return build(new PacketPlayOutEntityDestroy(entityId));
|
||||
}
|
||||
DataWatcher w = new DataWatcher();
|
||||
if (packet.getAction() == Action.UPDATE_PCT || packet.getAction() == Action.ADD) {
|
||||
float health = 300*packet.getPct();
|
||||
if (health == 0) health = 1;
|
||||
w.helper().setHealth(health);
|
||||
}
|
||||
if (packet.getAction() == Action.UPDATE_NAME || packet.getAction() == Action.ADD) {
|
||||
w.helper().setCustomName(packet.getName(), clientVersion);
|
||||
}
|
||||
if (packet.getAction() == Action.ADD) {
|
||||
w.helper().setEntityFlags((byte) 32);
|
||||
return build(new PacketPlayOutSpawnEntityLiving(entityId, new UUID(0, 0), EntityType.WITHER, new Location(null, 0,0,0), w));
|
||||
} else {
|
||||
return build(new PacketPlayOutEntityMetadata(entityId, w));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method taken from minecraft code used to convert double to int
|
||||
* @param paramDouble double value
|
||||
* @return int value
|
||||
*/
|
||||
private int floor(double paramDouble){
|
||||
int i = (int)paramDouble;
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts minecraft IChatBaseComponent into TAB's component class. Currently, does not support show_item hover event on 1.16+.
|
||||
* @param component - component to convert
|
||||
* @return converted component
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
private IChatBaseComponent fromNMSComponent(Object component) throws ReflectiveOperationException {
|
||||
if (!nms.ChatComponentText.isInstance(component)) return null; //paper
|
||||
IChatBaseComponent chat = new IChatBaseComponent((String) nms.ChatComponentText_text.get(component));
|
||||
Object modifier = nms.ChatBaseComponent_modifier.get(component);
|
||||
if (modifier != null) {
|
||||
chat.getModifier().setColor(fromNMSColor(nms.ChatModifier_color.get(modifier)));
|
||||
chat.getModifier().setBold((Boolean) nms.ChatModifier_bold.get(modifier));
|
||||
chat.getModifier().setItalic((Boolean) nms.ChatModifier_italic.get(modifier));
|
||||
chat.getModifier().setObfuscated((Boolean) nms.ChatModifier_obfuscated.get(modifier));
|
||||
chat.getModifier().setStrikethrough((Boolean) nms.ChatModifier_strikethrough.get(modifier));
|
||||
chat.getModifier().setUnderlined((Boolean) nms.ChatModifier_underlined.get(modifier));
|
||||
Object clickEvent = nms.ChatModifier_clickEvent.get(modifier);
|
||||
if (clickEvent != null) {
|
||||
chat.getModifier().onClick(EnumClickAction.valueOf(nms.ChatClickable_action.get(clickEvent).toString().toUpperCase()), (String) nms.ChatClickable_value.get(clickEvent));
|
||||
}
|
||||
Object hoverEvent = nms.ChatModifier_hoverEvent.get(modifier);
|
||||
if (hoverEvent != null) {
|
||||
EnumHoverAction action;
|
||||
IChatBaseComponent value;
|
||||
if (nms.getMinorVersion() >= 16) {
|
||||
//does not support show_item on 1.16+
|
||||
JsonObject json = (JsonObject) nms.ChatHoverable_serialize.invoke(hoverEvent);
|
||||
action = EnumHoverAction.valueOf(json.get("action").getAsString().toUpperCase());
|
||||
value = IChatBaseComponent.deserialize(json.get("contents").getAsJsonObject().toString());
|
||||
} else {
|
||||
action = EnumHoverAction.valueOf(nms.ChatHoverable_getAction.invoke(hoverEvent).toString().toUpperCase());
|
||||
value = fromNMSComponent(nms.ChatHoverable_getValue.invoke(hoverEvent));
|
||||
}
|
||||
chat.getModifier().onHover(action, value);
|
||||
}
|
||||
}
|
||||
for (Object extra : (List<Object>) nms.ChatBaseComponent_extra.get(component)) {
|
||||
chat.addExtra(fromNMSComponent(extra));
|
||||
}
|
||||
return chat;
|
||||
}
|
||||
|
||||
private TextColor fromNMSColor(Object color) throws ReflectiveOperationException {
|
||||
if (color == null) return null;
|
||||
if (nms.getMinorVersion() >= 16) {
|
||||
String name = (String) nms.ChatHexColor_name.get(color);
|
||||
if (name != null) {
|
||||
//legacy code
|
||||
return new TextColor(EnumChatFormat.valueOf(name.toUpperCase(Locale.US)));
|
||||
} else {
|
||||
int rgb = (int) nms.ChatHexColor_rgb.get(color);
|
||||
return new TextColor((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
|
||||
}
|
||||
} else {
|
||||
return new TextColor(EnumChatFormat.valueOf(((Enum)color).name()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TAB's IChatBaseComponent into minecraft's component. Currently, does not support hover event.
|
||||
* @param component - component to convert
|
||||
* @param clientVersion - client version used to decide RGB conversion
|
||||
* @return converted component
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object toNMSComponent(IChatBaseComponent component, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
Object obj;
|
||||
if (clientVersion.getMinorVersion() >= 16) {
|
||||
if (componentCacheModern.containsKey(component)) return componentCacheModern.get(component);
|
||||
obj = toNMSComponent0(component, clientVersion);
|
||||
if (componentCacheModern.size() > 10000) componentCacheModern.clear();
|
||||
componentCacheModern.put(component, obj);
|
||||
} else {
|
||||
if (componentCacheLegacy.containsKey(component)) return componentCacheLegacy.get(component);
|
||||
obj = toNMSComponent0(component, clientVersion);
|
||||
if (componentCacheLegacy.size() > 10000) componentCacheLegacy.clear();
|
||||
componentCacheLegacy.put(component, obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
//separate method to prevent extras counting cpu again due to recursion and finally showing higher usage than real
|
||||
private Object toNMSComponent0(IChatBaseComponent component, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (component == null) return null;
|
||||
Object chat = nms.newChatComponentText.newInstance(component.getText());
|
||||
Object modifier;
|
||||
Object clickEvent = component.getModifier().getClickEvent() == null ? null : nms.newChatClickable.newInstance(Enum.valueOf((Class<Enum>) nms.EnumClickAction,
|
||||
component.getModifier().getClickEvent().getAction().toString().toUpperCase()), component.getModifier().getClickEvent().getValue());
|
||||
if (nms.getMinorVersion() >= 16) {
|
||||
Object color = null;
|
||||
if (component.getModifier().getColor() != null) {
|
||||
if (clientVersion.getMinorVersion() >= 16) {
|
||||
color = nms.ChatHexColor_ofInt.invoke(null, (component.getModifier().getColor().getRed() << 16) + (component.getModifier().getColor().getGreen() << 8) + component.getModifier().getColor().getBlue());
|
||||
} else {
|
||||
color = nms.ChatHexColor_ofString.invoke(null, component.getModifier().getColor().getLegacyColor().toString().toLowerCase());
|
||||
}
|
||||
}
|
||||
Object hoverEvent = null;
|
||||
if (component.getModifier().getHoverEvent() != null) {
|
||||
Object nmsAction = nms.EnumHoverAction_a.invoke(null, component.getModifier().getHoverEvent().getAction().toString().toLowerCase());
|
||||
switch (component.getModifier().getHoverEvent().getAction()) {
|
||||
case SHOW_TEXT:
|
||||
hoverEvent = nms.newChatHoverable.newInstance(nmsAction, toNMSComponent0(component.getModifier().getHoverEvent().getValue(), clientVersion));
|
||||
break;
|
||||
case SHOW_ENTITY:
|
||||
hoverEvent = nms.EnumHoverAction_fromJson.invoke(nmsAction, ((ChatComponentEntity) component.getModifier().getHoverEvent().getValue()).toJson());
|
||||
break;
|
||||
case SHOW_ITEM:
|
||||
hoverEvent = nms.EnumHoverAction_fromLegacyComponent.invoke(nmsAction, toNMSComponent0(component.getModifier().getHoverEvent().getValue(), clientVersion));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
modifier = nms.newChatModifier.newInstance(
|
||||
color,
|
||||
component.getModifier().getBold(),
|
||||
component.getModifier().getItalic(),
|
||||
component.getModifier().getUnderlined(),
|
||||
component.getModifier().getStrikethrough(),
|
||||
component.getModifier().getObfuscated(),
|
||||
clickEvent, hoverEvent, null, null);
|
||||
} else {
|
||||
modifier = nms.newChatModifier.newInstance();
|
||||
if (component.getModifier().getColor() != null) nms.setField(modifier, nms.ChatModifier_color, Enum.valueOf((Class<Enum>) nms.EnumChatFormat, component.getModifier().getColor().getLegacyColor().toString()));
|
||||
nms.setField(modifier, nms.ChatModifier_bold, component.getModifier().getBold());
|
||||
nms.setField(modifier, nms.ChatModifier_italic, component.getModifier().getItalic());
|
||||
nms.setField(modifier, nms.ChatModifier_underlined, component.getModifier().getUnderlined());
|
||||
nms.setField(modifier, nms.ChatModifier_strikethrough, component.getModifier().getStrikethrough());
|
||||
nms.setField(modifier, nms.ChatModifier_obfuscated, component.getModifier().getObfuscated());
|
||||
if (clickEvent != null) nms.setField(modifier, nms.ChatModifier_clickEvent, clickEvent);
|
||||
if (component.getModifier().getHoverEvent() != null) {
|
||||
nms.setField(modifier, nms.ChatModifier_hoverEvent, nms.newChatHoverable.newInstance(nms.EnumHoverAction_a.invoke(null,
|
||||
component.getModifier().getHoverEvent().getAction().toString().toLowerCase()), toNMSComponent0(component.getModifier().getHoverEvent().getValue(), clientVersion)));
|
||||
}
|
||||
}
|
||||
nms.setField(chat, nms.ChatBaseComponent_modifier, modifier);
|
||||
for (IChatBaseComponent extra : component.getExtra()) {
|
||||
nms.ChatComponentText_addSibling.invoke(chat, toNMSComponent0(extra, clientVersion));
|
||||
}
|
||||
return chat;
|
||||
}
|
||||
|
||||
private Object newScoreboardObjective(String objectiveName) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return nms.newScoreboardObjective.newInstance(null, objectiveName, null, nms.newChatComponentText.newInstance(""), null);
|
||||
}
|
||||
return nms.newScoreboardObjective.newInstance(null, objectiveName, nms.IScoreboardCriteria_self.get(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.PipelineInjector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BukkitPipelineInjector extends PipelineInjector {
|
||||
|
||||
//nms storage
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BukkitPipelineInjector(){
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
|
||||
return BukkitChannelDuplexHandler::new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom channel duplex handler override
|
||||
*/
|
||||
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
|
||||
|
||||
//injected player
|
||||
private final TabPlayer player;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player
|
||||
* @param player - player to inject
|
||||
*/
|
||||
public BukkitChannelDuplexHandler(TabPlayer player) {
|
||||
Preconditions.checkNotNull(player, "player");
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object packet) {
|
||||
try {
|
||||
if (TAB.getInstance().getFeatureManager().onPacketReceive(player, packet)) return;
|
||||
super.channelRead(context, packet);
|
||||
} catch (Exception e){
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
|
||||
try {
|
||||
if (nms.PacketPlayOutPlayerInfo.isInstance(packet)) {
|
||||
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
|
||||
return;
|
||||
}
|
||||
if (antiOverrideTeams && nms.PacketPlayOutScoreboardTeam != null && nms.PacketPlayOutScoreboardTeam.isInstance(packet)) {
|
||||
modifyPlayers(packet);
|
||||
super.write(context, packet, channelPromise);
|
||||
return;
|
||||
}
|
||||
if (nms.PacketPlayOutScoreboardDisplayObjective.isInstance(packet)){
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
|
||||
}
|
||||
if (nms.PacketPlayOutScoreboardObjective.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, packet);
|
||||
}
|
||||
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
|
||||
} catch (Exception e){
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
|
||||
}
|
||||
try {
|
||||
super.write(context, packet, channelPromise);
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to forward packet " + packet.getClass().getSimpleName() + " to " + player.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all real players from team if packet does not come from TAB and reports this to override log
|
||||
* @param packetPlayOutScoreboardTeam - team packet
|
||||
* @throws ReflectiveOperationException
|
||||
* nmsGameMode
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
|
||||
long time = System.nanoTime();
|
||||
Collection<String> players = (Collection<String>) nms.PacketPlayOutScoreboardTeam_PLAYERS.get(packetPlayOutScoreboardTeam);
|
||||
String teamName = (String) nms.PacketPlayOutScoreboardTeam_NAME.get(packetPlayOutScoreboardTeam);
|
||||
if (players == null) return;
|
||||
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
|
||||
Collection<String> newList = new ArrayList<>();
|
||||
for (String entry : players) {
|
||||
TabPlayer p = getPlayer(entry);
|
||||
if (p == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(p.getTeamName())) {
|
||||
logTeamOverride(teamName, p.getName(), p.getTeamName());
|
||||
} else {
|
||||
newList.add(entry);
|
||||
}
|
||||
}
|
||||
nms.setField(packetPlayOutScoreboardTeam, nms.PacketPlayOutScoreboardTeam_PLAYERS, newList);
|
||||
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
private TabPlayer getPlayer(String name) {
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (p.getNickname().equals(name)) return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
|
||||
import com.earth2me.essentials.Essentials;
|
||||
|
||||
import me.neznamy.tab.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
|
||||
/**
|
||||
* Bukkit registry to register bukkit-only and universal placeholders
|
||||
*/
|
||||
public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
|
||||
/** Number formatter for 2 decimal places */
|
||||
private final NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.US);
|
||||
|
||||
private Object chat;
|
||||
private final Plugin essentials = Bukkit.getPluginManager().getPlugin("Essentials");
|
||||
private Object server;
|
||||
private Field recentTps;
|
||||
private boolean paperTps;
|
||||
private boolean paperMspt;
|
||||
private Method playerIsAfk;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
*/
|
||||
public BukkitPlaceholderRegistry() {
|
||||
numberFormat.setMaximumFractionDigits(2);
|
||||
try {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<?> rspChat = Bukkit.getServicesManager().getRegistration(Class.forName("net.milkbowl.vault.chat.Chat"));
|
||||
if (rspChat != null) chat = rspChat.getProvider();
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
//modded server without vault
|
||||
}
|
||||
try {
|
||||
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
recentTps = server.getClass().getField("recentTps");
|
||||
} catch (ReflectiveOperationException e) {
|
||||
//not spigot
|
||||
}
|
||||
try {
|
||||
Bukkit.class.getMethod("getTPS");
|
||||
paperTps = true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
//not paper
|
||||
}
|
||||
try {
|
||||
Bukkit.class.getMethod("getAverageTickTime");
|
||||
paperMspt = true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
//not paper
|
||||
}
|
||||
try {
|
||||
playerIsAfk = Player.class.getMethod("isAfk");
|
||||
} catch (NoSuchMethodException e) {
|
||||
//not purpur
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void registerPlaceholders(PlaceholderManager manager) {
|
||||
super.registerPlaceholders(manager);
|
||||
manager.registerPlayerPlaceholder("%displayname%", 500, p -> ((Player) p.getPlayer()).getDisplayName());
|
||||
if (paperTps) {
|
||||
manager.registerServerPlaceholder("%tps%", 1000, () -> formatTPS(Bukkit.getTPS()[0]));
|
||||
} else if (recentTps != null) {
|
||||
manager.registerServerPlaceholder("%tps%", 1000, () -> {
|
||||
try {
|
||||
return formatTPS(((double[]) recentTps.get(server))[0]);
|
||||
} catch (IllegalAccessException e) {
|
||||
return "-1";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
manager.registerServerPlaceholder("%tps%", -1, () -> "-1").enableTriggerMode();
|
||||
}
|
||||
if (paperMspt) {
|
||||
manager.registerServerPlaceholder("%mspt%", 1000, () -> numberFormat.format(Bukkit.getAverageTickTime()));
|
||||
}
|
||||
manager.registerPlayerPlaceholder("%afk%", 500, p -> {
|
||||
if (essentials != null && ((Essentials)essentials).getUser(p.getUniqueId()).isAfk()) return true;
|
||||
if (playerIsAfk == null) return false;
|
||||
try {
|
||||
return playerIsAfk.invoke(p.getPlayer());
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get AFK status of " + p.getName() + " using Purpur", exception);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
manager.registerPlayerPlaceholder("%essentialsnick%", 1000, p -> {
|
||||
String nickname = null;
|
||||
if (essentials != null)
|
||||
nickname = ((Essentials)essentials).getUser(p.getUniqueId()).getNickname();
|
||||
return nickname == null || nickname.length() == 0 ? p.getName() : nickname;
|
||||
});
|
||||
if (chat != null) {
|
||||
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> ((Chat) chat).getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> ((Chat) chat).getPlayerSuffix((Player) p.getPlayer()));
|
||||
} else {
|
||||
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "").enableTriggerMode();
|
||||
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "").enableTriggerMode();
|
||||
}
|
||||
manager.registerPlayerPlaceholder("%health%", 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
|
||||
}
|
||||
|
||||
private String formatTPS(double tps) {
|
||||
return numberFormat.format(Math.min(20, tps));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.earth2me.essentials.Essentials;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import me.neznamy.tab.platforms.bukkit.event.TabLoadEvent;
|
||||
import me.neznamy.tab.platforms.bukkit.event.TabPlayerLoadEvent;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PetFix;
|
||||
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.features.unlimitedtags.BukkitNameTagX;
|
||||
import me.neznamy.tab.platforms.bukkit.permission.Vault;
|
||||
import me.neznamy.tab.shared.Platform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.permission.LuckPerms;
|
||||
import me.neznamy.tab.shared.permission.None;
|
||||
import me.neznamy.tab.shared.permission.PermissionPlugin;
|
||||
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Implementation of Platform interface for Bukkit platform
|
||||
*/
|
||||
public class BukkitPlatform extends Platform {
|
||||
|
||||
/** Plugin instance for registering tasks and events */
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
/** Variables checking presence of other plugins to hook into */
|
||||
private final boolean placeholderAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
|
||||
private boolean libsDisguises = Bukkit.getPluginManager().isPluginEnabled("LibsDisguises");
|
||||
private final Plugin essentials = Bukkit.getPluginManager().getPlugin("Essentials");
|
||||
private Plugin viaVersion;
|
||||
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport");
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin parameter
|
||||
* @param plugin
|
||||
* plugin instance
|
||||
*/
|
||||
public BukkitPlatform(JavaPlugin plugin) {
|
||||
super(new BukkitPacketBuilder());
|
||||
Preconditions.checkNotNull(plugin, "plugin");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionPlugin detectPermissionPlugin() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
|
||||
return new LuckPerms(getPluginVersion("LuckPerms"));
|
||||
} else if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
|
||||
if (provider == null) return new None();
|
||||
return new Vault(provider.getProvider(), getPluginVersion("Vault"));
|
||||
} else {
|
||||
return new None();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFeatures() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) {
|
||||
try {
|
||||
Class.forName("com.viaversion.viaversion.api.Via");
|
||||
viaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion");
|
||||
} catch (ClassNotFoundException e) {
|
||||
TAB.getInstance().sendConsoleMessage("&cAn outdated version of ViaVersion (" + getPluginVersion("ViaVersion") + ") was detected.", true);
|
||||
TAB.getInstance().sendConsoleMessage("&cTAB only supports ViaVersion 4.0.0 and above. Disabling ViaVersion hook.", true);
|
||||
TAB.getInstance().sendConsoleMessage("&cThis might cause problems, such as limitations still being present for latest MC clients as well as RGB not working.", true);
|
||||
}
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Tablisknu")) {
|
||||
TAB.getInstance().sendConsoleMessage("&cDetected plugin \"Tablisknu\", which causes TAB to not work properly. Consider removing the plugin.", true);
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("SkBee")) {
|
||||
TAB.getInstance().sendConsoleMessage("&cDetected plugin \"SkBee\", which causes TAB's scoreboard to not show. Consider removing the plugin.", true);
|
||||
}
|
||||
TAB tab = TAB.getInstance();
|
||||
if (tab.getConfiguration().isPipelineInjection())
|
||||
tab.getFeatureManager().registerFeature(TabConstants.Feature.PIPELINE_INJECTION, new BukkitPipelineInjector());
|
||||
new BukkitPlaceholderRegistry().registerPlaceholders(tab.getPlaceholderManager());
|
||||
if (tab.getConfiguration().getConfig().getBoolean("scoreboard-teams.enabled", true)) {
|
||||
if (tab.getConfiguration().getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false) && tab.getServerVersion().getMinorVersion() >= 8) {
|
||||
tab.getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, new BukkitNameTagX(plugin));
|
||||
} else {
|
||||
tab.getFeatureManager().registerFeature(TabConstants.Feature.NAME_TAGS, new NameTag());
|
||||
}
|
||||
}
|
||||
tab.loadUniversalFeatures();
|
||||
if (tab.getConfiguration().getConfig().getBoolean("bossbar.enabled", false)) {
|
||||
if (tab.getServerVersion().getMinorVersion() < 9) {
|
||||
tab.getFeatureManager().registerFeature(TabConstants.Feature.BOSS_BAR, new WitherBossBar(plugin));
|
||||
} else {
|
||||
tab.getFeatureManager().registerFeature(TabConstants.Feature.BOSS_BAR, new BossBarManagerImpl());
|
||||
}
|
||||
}
|
||||
if (tab.getServerVersion().getMinorVersion() >= 9 && tab.getConfiguration().getConfig().getBoolean("fix-pet-names.enabled", false))
|
||||
tab.getFeatureManager().registerFeature(TabConstants.Feature.PET_FIX, new PetFix());
|
||||
if (tab.getConfiguration().getConfig().getBoolean("per-world-playerlist.enabled", false))
|
||||
tab.getFeatureManager().registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, new PerWorldPlayerList(plugin));
|
||||
if (placeholderAPI && tab.getConfiguration().getConfig().getBoolean("placeholders.register-tab-expansion", true)) {
|
||||
BukkitTabExpansion expansion = new BukkitTabExpansion();
|
||||
expansion.register();
|
||||
TAB.getInstance().getPlaceholderManager().setTabExpansion(expansion);
|
||||
}
|
||||
for (Player p : getOnlinePlayers()) {
|
||||
tab.addPlayer(new BukkitTabPlayer(p, getProtocolVersion(p)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPluginVersion(String plugin) {
|
||||
Preconditions.checkNotNull(plugin, "plugin");
|
||||
Plugin pl = Bukkit.getPluginManager().getPlugin(plugin);
|
||||
return pl == null ? null : pl.getDescription().getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API
|
||||
* @return online players from Bukkit API
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Player[] getOnlinePlayers() {
|
||||
try {
|
||||
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
|
||||
if (players instanceof Player[]) {
|
||||
//1.7-
|
||||
return (Player[]) players;
|
||||
} else {
|
||||
//1.8+
|
||||
return ((Collection<Player>)players).toArray(new Player[0]);
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get online players", e);
|
||||
return new Player[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(String identifier) {
|
||||
Preconditions.checkNotNull(identifier, "identifier");
|
||||
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
|
||||
if (identifier.startsWith("%rel_")) {
|
||||
//relational placeholder
|
||||
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRelationalRefresh(identifier), (viewer, target) ->
|
||||
placeholderAPI ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
|
||||
} else {
|
||||
//normal placeholder
|
||||
if (identifier.startsWith("%sync:")) {
|
||||
int refresh = pl.getServerPlaceholderRefreshIntervals().getOrDefault(identifier,
|
||||
pl.getPlayerPlaceholderRefreshIntervals().getOrDefault(identifier, pl.getDefaultRefresh()));
|
||||
String syncedPlaceholder = "%" + identifier.substring(6, identifier.length()-1) + "%";
|
||||
pl.registerPlaceholder(new PlayerPlaceholderImpl(identifier, refresh, null) {
|
||||
|
||||
@Override
|
||||
public Object request(TabPlayer p) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
long time = System.nanoTime();
|
||||
updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(getIdentifier(), System.nanoTime()-time);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (pl.getServerPlaceholderRefreshIntervals().containsKey(identifier)) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, pl.getServerPlaceholderRefreshIntervals().get(identifier), () ->
|
||||
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
|
||||
} else {
|
||||
int refresh = pl.getPlayerPlaceholderRefreshIntervals().getOrDefault(identifier, pl.getDefaultRefresh());
|
||||
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
|
||||
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callLoadEvent() {
|
||||
Bukkit.getPluginManager().callEvent(new TabLoadEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callLoadEvent(TabPlayer player) {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> Bukkit.getPluginManager().callEvent(new TabPlayerLoadEvent(player)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns status of LibsDisguises plugin presence
|
||||
* @return {@code true} if plugin is enabled, {@code false} if not
|
||||
*/
|
||||
public boolean isLibsDisguisesEnabled() {
|
||||
return libsDisguises;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets LibsDisguises presence status to provided value. This is used
|
||||
* to disable LibsDisguises hook in case the plugin is not correctly loaded
|
||||
* for any reason to avoid error spam in the hook.
|
||||
* @param enabled
|
||||
* New status of LibsDisguises presence
|
||||
*/
|
||||
public void setLibsDisguisesEnabled(boolean enabled) {
|
||||
libsDisguises = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Essentials' main class if the plugin is installed, {@code null} if not
|
||||
* @return Essentials instance or {@code null} if plugin is not installed
|
||||
*/
|
||||
public Essentials getEssentials() {
|
||||
return (Essentials) essentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProxy() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigName() {
|
||||
return "bukkitconfig.yml";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets protocol version of requested player and returns it.
|
||||
* @param player
|
||||
* Player to get protocol version of
|
||||
* @return protocol version of the player
|
||||
*/
|
||||
public int getProtocolVersion(Player player) {
|
||||
if (protocolSupport){
|
||||
int version = getProtocolVersionPS(player);
|
||||
//some PS versions return -1 on unsupported server versions instead of throwing exception
|
||||
if (version != -1 && version < TAB.getInstance().getServerVersion().getNetworkId()) return version;
|
||||
}
|
||||
if (viaVersion != null) {
|
||||
return getProtocolVersionVia(player, 0);
|
||||
}
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns protocol version of requested player using ProtocolSupport
|
||||
* @param player
|
||||
* Player to get protocol version of
|
||||
* @return protocol version of the player using ProtocolSupport
|
||||
*/
|
||||
private int getProtocolVersionPS(Player player){
|
||||
try {
|
||||
Object protocolVersion = Class.forName("protocolsupport.api.ProtocolSupportAPI").getMethod("getProtocolVersion", Player.class).invoke(null, player);
|
||||
int version = (int) protocolVersion.getClass().getMethod("getId").invoke(protocolVersion);
|
||||
TAB.getInstance().debug("ProtocolSupport returned protocol version " + version + " for " + player.getName() + "(online=" + player.isOnline() + ")");
|
||||
return version;
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError(String.format("Failed to get protocol version of %s using ProtocolSupport", player.getName()), e);
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns protocol version of requested player using ViaVersion
|
||||
* @param player
|
||||
* Player to get protocol version of
|
||||
* @return protocol version of the player using ViaVersion
|
||||
*/
|
||||
private int getProtocolVersionVia(Player player, int retryLevel){
|
||||
try {
|
||||
if (retryLevel == 10) {
|
||||
TAB.getInstance().debug("Failed to get protocol version of " + player.getName() + " after 10 retries");
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
int version = Via.getAPI().getPlayerVersion(player.getUniqueId());
|
||||
if (version == -1) {
|
||||
if (!player.isOnline()) return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
Thread.sleep(5);
|
||||
return getProtocolVersionVia(player, retryLevel + 1);
|
||||
}
|
||||
TAB.getInstance().debug("ViaVersion returned protocol version " + version + " for " + player.getName() + "(online=" + player.isOnline() + ")");
|
||||
return version;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return -1;
|
||||
} catch (Exception | LinkageError e) {
|
||||
TAB.getInstance().getErrorManager().printError(String.format("Failed to get protocol version of %s using ViaVersion v%s", player.getName(), viaVersion.getDescription().getVersion()), e);
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import me.neznamy.tab.api.protocol.Skin;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.boss.BarColor;
|
||||
import org.bukkit.boss.BarFlag;
|
||||
import org.bukkit.boss.BarStyle;
|
||||
import org.bukkit.boss.BossBar;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import com.earth2me.essentials.Essentials;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossFlag;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.libraryaddict.disguise.DisguiseAPI;
|
||||
import me.neznamy.tab.api.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.ITabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for Bukkit platform
|
||||
*/
|
||||
public class BukkitTabPlayer extends ITabPlayer {
|
||||
|
||||
/** Player's NMS handle (EntityPlayer), preloading for speed */
|
||||
private Object handle;
|
||||
|
||||
/** Player's connection for sending packets, preloading for speed */
|
||||
private Object playerConnection;
|
||||
|
||||
/** Bukkit BossBars the player can currently see */
|
||||
private final Map<UUID, BossBar> bossBars = new HashMap<>();
|
||||
|
||||
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
|
||||
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given bukkit player and protocol version
|
||||
* @param p
|
||||
* bukkit player
|
||||
* @param protocolVersion
|
||||
* Player's protocol network id
|
||||
*/
|
||||
public BukkitTabPlayer(Player p, int protocolVersion){
|
||||
super(p, p.getUniqueId(), p.getName(), "N/A", p.getWorld().getName(), protocolVersion);
|
||||
try {
|
||||
handle = NMSStorage.getInstance().getHandle.invoke(player);
|
||||
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
|
||||
if (NMSStorage.getInstance().CHANNEL != null)
|
||||
channel = (Channel) NMSStorage.getInstance().CHANNEL.get(NMSStorage.getInstance().NETWORK_MANAGER.get(playerConnection));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get playerConnection or channel of " + p.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
Preconditions.checkNotNull(permission, "permission");
|
||||
long time = System.nanoTime();
|
||||
boolean value = getPlayer().hasPermission(permission);
|
||||
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
try {
|
||||
int ping = NMSStorage.getInstance().PING.getInt(handle);
|
||||
if (ping > 10000 || ping < 0) ping = -1;
|
||||
return ping;
|
||||
} catch (IllegalAccessException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Object nmsPacket) {
|
||||
if (nmsPacket == null || !getPlayer().isOnline()) return;
|
||||
long time = System.nanoTime();
|
||||
try {
|
||||
if (nmsPacket instanceof PacketPlayOutBoss) {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
|
||||
handle((PacketPlayOutBoss) nmsPacket);
|
||||
} else {
|
||||
handleVia((PacketPlayOutBoss) nmsPacket);
|
||||
}
|
||||
} else {
|
||||
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
|
||||
}
|
||||
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles PacketPlayOutBoss packet send request using Bukkit API,
|
||||
* since the API offers everything we need and makes us not need to
|
||||
* deal with NMS code at all.
|
||||
* @param packet
|
||||
* packet request to handle using Bukkit API
|
||||
*/
|
||||
private void handle(PacketPlayOutBoss packet) {
|
||||
BossBar bar = bossBars.get(packet.getId());
|
||||
if (packet.getAction() == PacketPlayOutBoss.Action.ADD) {
|
||||
if (bossBars.containsKey(packet.getId())) return;
|
||||
bar = Bukkit.createBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
|
||||
BarColor.valueOf(packet.getColor().name()),
|
||||
BarStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
if (packet.isCreateWorldFog()) bar.addFlag(BarFlag.CREATE_FOG);
|
||||
if (packet.isDarkenScreen()) bar.addFlag(BarFlag.DARKEN_SKY);
|
||||
if (packet.isPlayMusic()) bar.addFlag(BarFlag.PLAY_BOSS_MUSIC);
|
||||
bar.setProgress(packet.getPct());
|
||||
bossBars.put(packet.getId(), bar);
|
||||
bar.addPlayer(getPlayer());
|
||||
return;
|
||||
}
|
||||
if (bar == null) return; //no idea how
|
||||
switch (packet.getAction()) {
|
||||
case REMOVE:
|
||||
bar.removePlayer(getPlayer());
|
||||
bossBars.remove(packet.getId());
|
||||
break;
|
||||
case UPDATE_PCT:
|
||||
bar.setProgress(packet.getPct());
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
bar.setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
bar.setColor(BarColor.valueOf(packet.getColor().name()));
|
||||
bar.setStyle(BarStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
bar = bossBars.get(packet.getId());
|
||||
processFlag(bar, packet.isCreateWorldFog(), BarFlag.CREATE_FOG);
|
||||
processFlag(bar, packet.isDarkenScreen(), BarFlag.DARKEN_SKY);
|
||||
processFlag(bar, packet.isPlayMusic(), BarFlag.PLAY_BOSS_MUSIC);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles PacketPlayOutBoss packet request for 1.9+ clients on
|
||||
* 1.8 servers using ViaVersion API instead of using Wither.
|
||||
* @param packet
|
||||
* packet request to handle using ViaVersion API
|
||||
*/
|
||||
private void handleVia(PacketPlayOutBoss packet) {
|
||||
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar;
|
||||
switch (packet.getAction()) {
|
||||
case ADD:
|
||||
if (viaBossBars.containsKey(packet.getId())) return;
|
||||
bar = Via.getAPI().legacyAPI().createLegacyBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16),
|
||||
packet.getPct(),
|
||||
BossColor.valueOf(packet.getColor().name()),
|
||||
BossStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
//fog missing from via API
|
||||
if (packet.isDarkenScreen()) bar.addFlag(BossFlag.DARKEN_SKY);
|
||||
if (packet.isPlayMusic()) bar.addFlag(BossFlag.PLAY_BOSS_MUSIC);
|
||||
viaBossBars.put(packet.getId(), bar);
|
||||
bar.addPlayer(getPlayer().getUniqueId());
|
||||
break;
|
||||
case REMOVE:
|
||||
viaBossBars.get(packet.getId()).removePlayer(getPlayer().getUniqueId());
|
||||
viaBossBars.remove(packet.getId());
|
||||
break;
|
||||
case UPDATE_PCT:
|
||||
viaBossBars.get(packet.getId()).setHealth(packet.getPct());
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
viaBossBars.get(packet.getId()).setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16));
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
viaBossBars.get(packet.getId()).setColor(BossColor.valueOf(packet.getColor().name()));
|
||||
viaBossBars.get(packet.getId()).setStyle(BossStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
bar = viaBossBars.get(packet.getId());
|
||||
//fog missing from via API
|
||||
processFlagVia(bar, packet.isDarkenScreen(), BossFlag.DARKEN_SKY);
|
||||
processFlagVia(bar, packet.isPlayMusic(), BossFlag.PLAY_BOSS_MUSIC);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets BossBar flag to requested target value.
|
||||
* @param bar
|
||||
* BossBar to set flag of
|
||||
* @param targetValue
|
||||
* Target value of the flag
|
||||
* @param flag
|
||||
* Flag to set value of
|
||||
*/
|
||||
private void processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
|
||||
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
|
||||
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets BossBar flag to requested target value.
|
||||
* @param bar
|
||||
* BossBar to set flag of
|
||||
* @param targetValue
|
||||
* Target value of the flag
|
||||
* @param flag
|
||||
* Flag to set value of
|
||||
*/
|
||||
private void processFlagVia(com.viaversion.viaversion.api.legacy.bossbar.BossBar bar, boolean targetValue, BossFlag flag) {
|
||||
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
|
||||
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return getPlayer().hasPotionEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
try {
|
||||
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).isLibsDisguisesEnabled()) return false;
|
||||
return DisguiseAPI.isDisguised(getPlayer());
|
||||
} catch (LinkageError e) {
|
||||
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
|
||||
TAB.getInstance().getErrorManager().printError("Failed to check disguise status using LibsDisguises", e);
|
||||
((BukkitPlatform)TAB.getInstance().getPlatform()).setLibsDisguisesEnabled(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Skin getSkin() {
|
||||
try {
|
||||
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get("textures");
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
return new Skin(property.getValue(), property.getSignature());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get skin of " + getName(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return (Player) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer().isOnline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
Essentials essentials = ((BukkitPlatform)TAB.getInstance().getPlatform()).getEssentials();
|
||||
if (essentials != null && essentials.getUser(getUniqueId()).isVanished()) return true;
|
||||
List<MetadataValue> metadata = getPlayer().getMetadata("vanished");
|
||||
return !metadata.isEmpty() && metadata.get(0).asBoolean();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().getGameMode().getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.*;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Main class for Bukkit platform
|
||||
*/
|
||||
public class Main extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable(){
|
||||
getLogger().info(EnumChatFormat.color("&7Server version: " + Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")"));
|
||||
if (!isVersionSupported()){
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
BukkitPlatform platform = new BukkitPlatform(this);
|
||||
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]),
|
||||
Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")", getDataFolder(), getLogger()));
|
||||
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
|
||||
getLogger().info(EnumChatFormat.color("&cUnknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
|
||||
}
|
||||
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(platform), this);
|
||||
TAB.getInstance().load();
|
||||
Metrics metrics = new Metrics(this, 5304);
|
||||
metrics.addCustomChart(new SimplePie("unlimited_nametag_mode_enabled", () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie("placeholderapi", () -> Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI") ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie("permission_system", () -> TAB.getInstance().getGroupManager().getPlugin().getName()));
|
||||
metrics.addCustomChart(new SimplePie("server_version", () -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
|
||||
PluginCommand cmd = Bukkit.getPluginCommand("tab");
|
||||
if (cmd == null) return;
|
||||
TABCommand command = new TABCommand();
|
||||
cmd.setExecutor(command);
|
||||
cmd.setTabCompleter(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
//null check due to L31 return making L34 not run
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all used NMS classes, constructors, fields and methods.
|
||||
* Returns {@code true} if everything went successfully and version is marked as compatible,
|
||||
* {@code false} if anything went wrong or version is not marked as compatible.
|
||||
* @return {@code true} if server version is compatible, {@code false} if not
|
||||
*/
|
||||
private boolean isVersionSupported(){
|
||||
List<String> supportedVersions = Arrays.asList(
|
||||
"v1_5_R1", "v1_5_R2", "v1_5_R3", "v1_6_R1", "v1_6_R2", "v1_6_R3",
|
||||
"v1_7_R1", "v1_7_R2", "v1_7_R3", "v1_7_R4", "v1_8_R1", "v1_8_R2", "v1_8_R3",
|
||||
"v1_9_R1", "v1_9_R2", "v1_10_R1", "v1_11_R1", "v1_12_R1", "v1_13_R1", "v1_13_R2",
|
||||
"v1_14_R1", "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1", "v1_18_R1", "v1_18_R2");
|
||||
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
try {
|
||||
long time = System.currentTimeMillis();
|
||||
NMSStorage.setInstance(new NMSStorage());
|
||||
if (supportedVersions.contains(serverPackage)) {
|
||||
getLogger().info(EnumChatFormat.color("&7Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
|
||||
return true;
|
||||
} else {
|
||||
getLogger().info(EnumChatFormat.color("&cNo compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on 1.5.x - 1.18.2. Disabling just to stay safe."));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (supportedVersions.contains(serverPackage)) {
|
||||
getLogger().log(Level.SEVERE, EnumChatFormat.color("&cYour server version is marked as compatible, but a compatibility issue was found. Please report the error below (include your server version & fork too)"), ex);
|
||||
} else {
|
||||
getLogger().info(EnumChatFormat.color("&cYour server version is completely unsupported. This plugin version only supports 1.5.x - 1.18.2. Disabling."));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command handler for /tab command
|
||||
*/
|
||||
public static class TABCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
if (TAB.getInstance().isDisabled()) {
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
|
||||
sender.sendMessage(EnumChatFormat.color(message));
|
||||
}
|
||||
} else {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof Player) {
|
||||
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
|
||||
if (p == null) return true; //player not loaded correctly
|
||||
}
|
||||
TAB.getInstance().getCommand().execute(p, args);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof Player) {
|
||||
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
|
||||
if (p == null) return new ArrayList<>(); //player not loaded correctly
|
||||
}
|
||||
return TAB.getInstance().getCommand().complete(p, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.platforms.bukkit.event;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Bukkit event that is called each time TAB fully loads. This includes server startup, reload,
|
||||
* /tab reload and /plugman reload tab
|
||||
*
|
||||
* @deprecated for removal, use {@link me.neznamy.tab.api.event.plugin.TabLoadEvent}
|
||||
*/
|
||||
@Deprecated
|
||||
public class TabLoadEvent extends Event {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers(){
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package me.neznamy.tab.platforms.bukkit.event;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Bukkit event that is called when player is successfully loaded after joining. This also includes plugin reloading.
|
||||
*
|
||||
* @deprecated for removal, use {@link me.neznamy.tab.api.event.player.PlayerLoadEvent}
|
||||
*/
|
||||
@Deprecated
|
||||
public class TabPlayerLoadEvent extends Event {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
|
||||
private final TabPlayer player;
|
||||
|
||||
public TabPlayerLoadEvent(TabPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers(){
|
||||
return getHandlerList();
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public TabPlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.TabExpansion;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* TAB's expansion for PlaceholderAPI
|
||||
*/
|
||||
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
|
||||
|
||||
/** Map holding all placeholder values for all players */
|
||||
private final Map<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean persist(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRegister(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getAuthor(){
|
||||
return "NEZNAMY";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier(){
|
||||
return "tab";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getVersion() {
|
||||
return TabConstants.PLUGIN_VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onPlaceholderRequest(Player player, @NotNull String identifier){
|
||||
if (player == null) return "";
|
||||
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
|
||||
if (identifier.startsWith("replace_")) {
|
||||
String placeholder = "%" + identifier.substring(8) + "%";
|
||||
String output = PlaceholderAPI.setPlaceholders(player, placeholder);
|
||||
return TAB.getInstance().getPlaceholderManager().findReplacement(placeholder, output);
|
||||
}
|
||||
return values.computeIfAbsent(p, pl -> new HashMap<>()).get(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScoreboardVisible(TabPlayer player, boolean visible) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put("scoreboard_visible", visible ? "Enabled" : "Disabled");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScoreboardName(TabPlayer player, String name) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put("scoreboard_name", name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBossBarVisible(TabPlayer player, boolean visible) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put("bossbar_visible", visible ? "Enabled" : "Disabled");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNameTagPreview(TabPlayer player, boolean previewing) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put("ntpreview", previewing ? "Enabled" : "Disabled");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaceholderValue(TabPlayer player, String placeholder, String value) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put("placeholder_" + placeholder.substring(1, placeholder.length()-1), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPropertyValue(TabPlayer player, String property, String value) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put(property, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRawPropertyValue(TabPlayer player, String property, String value) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put(property + "_raw", value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
|
||||
/**
|
||||
* Per-world-PlayerList feature handler
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
|
||||
//plugin instance
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
//config options
|
||||
private final boolean allowBypass = TAB.getInstance().getConfiguration().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
private final List<String> ignoredWorlds = TAB.getInstance().getConfiguration().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
|
||||
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfiguration().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
* @param plugin - plugin instance
|
||||
*/
|
||||
public PerWorldPlayerList(JavaPlugin plugin) {
|
||||
super("Per world PlayerList", null);
|
||||
this.plugin = plugin;
|
||||
TAB.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
Bukkit.getScheduler().runTask(plugin, () -> Bukkit.getOnlinePlayers().forEach(this::checkPlayer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload(){
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
}
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
private void checkPlayer(Player p) {
|
||||
for (Player all : Bukkit.getOnlinePlayers()){
|
||||
if (all == p) continue;
|
||||
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
|
||||
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
|
||||
if (!shouldSee(all, p) && all.canSee(p)) all.hidePlayer(p);
|
||||
if (shouldSee(all, p) && !all.canSee(p)) all.showPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSee(Player viewer, Player target) {
|
||||
if (target == viewer) return true;
|
||||
if ((allowBypass && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || ignoredWorlds.contains(viewer.getWorld().getName())) return true;
|
||||
String viewerWorldGroup = viewer.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
|
||||
String targetWorldGroup = target.getWorld().getName() + "-default";
|
||||
for (Entry<String, List<String>> group : sharedWorlds.entrySet()) {
|
||||
if (group.getValue() != null) {
|
||||
if (group.getValue().contains(viewer.getWorld().getName())) viewerWorldGroup = group.getKey();
|
||||
if (group.getValue().contains(target.getWorld().getName())) targetWorldGroup = group.getKey();
|
||||
}
|
||||
}
|
||||
return viewerWorldGroup.equals(targetWorldGroup);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
|
||||
/**
|
||||
* A feature to disable minecraft 1.9+ feature making tamed animals
|
||||
* with custom names copy NameTag properties of their owner.
|
||||
* This is achieved by listening to entity spawn (<1.15) / entity metadata
|
||||
* packets and removing owner field from the DataWatcher list.
|
||||
* Since 1.16 this results in client sending entity use packet twice,
|
||||
* so we must cancel the 2nd one to prevent double toggle.
|
||||
*/
|
||||
public class PetFix extends TabFeature {
|
||||
|
||||
//nms storage
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
|
||||
//DataWatcher position of pet owner field
|
||||
private final int petOwnerPosition = getPetOwnerPosition();
|
||||
|
||||
//logger of last interacts to prevent feature not working on 1.16
|
||||
private final WeakHashMap<TabPlayer, Long> lastInteractFix = new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
* Since 1.16, client sends interact packet twice for entities affected
|
||||
* by removed owner field. Because of that, we need to cancel the duplicated
|
||||
* packet to avoid double toggle and preventing the entity from getting
|
||||
* its pose changed. The duplicated packet is usually sent instantly in the
|
||||
* same millisecond, however, when installing ProtocolLib with MyPet, the delay
|
||||
* is up to 3 ticks. When holding right-click on an entity, interact is sent every
|
||||
* 200 milliseconds, which is the value we should not go above. Optimal value is
|
||||
* therefore between 150 and 200 milliseconds.
|
||||
*/
|
||||
private static final int INTERACT_COOLDOWN = 160;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
*/
|
||||
public PetFix() {
|
||||
super("Pet name fix", null);
|
||||
TAB.getInstance().debug("Loaded PetFix feature");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns position of pet owner field based on server version
|
||||
* @return position of pet owner field based on server version
|
||||
*/
|
||||
private int getPetOwnerPosition() {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
//1.17.x, 1.18.x
|
||||
return 18;
|
||||
} else if (nms.getMinorVersion() >= 15) {
|
||||
//1.15.x, 1.16.x
|
||||
return 17;
|
||||
} else if (nms.getMinorVersion() >= 14) {
|
||||
//1.14.x
|
||||
return 16;
|
||||
} else if (nms.getMinorVersion() >= 10) {
|
||||
//1.10.x - 1.13.x
|
||||
return 14;
|
||||
} else {
|
||||
//1.9.x
|
||||
return 13;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a packet if previous one arrived with no delay to prevent double toggle on 1.16
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
@Override
|
||||
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
|
||||
if (nms.PacketPlayInUseEntity.isInstance(packet)) {
|
||||
if (lastInteractFix.containsKey(sender) && (System.currentTimeMillis() - lastInteractFix.get(sender) < INTERACT_COOLDOWN)) {
|
||||
//last interact packet was sent right now, cancelling to prevent double-toggle due to this feature enabled
|
||||
return true;
|
||||
}
|
||||
if (isInteract(nms.PacketPlayInUseEntity_ACTION.get(packet))) {
|
||||
//this is the first packet, saving player so the next packet can be cancelled
|
||||
lastInteractFix.put(sender, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isInteract(Object action) {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
return nms.PacketPlayInUseEntity$d.isInstance(action);
|
||||
} else {
|
||||
return action.toString().equals("INTERACT");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes pet owner field from DataWatcher
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
|
||||
if (nms.PacketPlayOutEntityMetadata.isInstance(packet)) {
|
||||
Object removedEntry = null;
|
||||
List<Object> items = (List<Object>) nms.PacketPlayOutEntityMetadata_LIST.get(packet);
|
||||
if (items == null) return;
|
||||
try {
|
||||
for (Object item : items) {
|
||||
if (item == null) continue;
|
||||
if (nms.DataWatcherObject_SLOT.getInt(nms.DataWatcherItem_TYPE.get(item)) == petOwnerPosition) {
|
||||
Object value = nms.DataWatcherItem_VALUE.get(item);
|
||||
if (value instanceof java.util.Optional || value instanceof com.google.common.base.Optional) {
|
||||
removedEntry = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ConcurrentModificationException e) {
|
||||
//no idea how can this list change in another thread since it's created for the packet but whatever, try again
|
||||
onPacketSend(receiver, packet);
|
||||
}
|
||||
if (removedEntry != null) items.remove(removedEntry);
|
||||
} else if (nms.PacketPlayOutSpawnEntityLiving.isInstance(packet) && nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER != null) {
|
||||
//<1.15
|
||||
DataWatcher watcher = DataWatcher.fromNMS(nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER.get(packet));
|
||||
DataWatcherItem petOwner = watcher.getItem(petOwnerPosition);
|
||||
if (petOwner != null && (petOwner.getValue() instanceof java.util.Optional || petOwner.getValue() instanceof com.google.common.base.Optional)) {
|
||||
watcher.removeValue(petOwnerPosition);
|
||||
nms.setField(packet, nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER, watcher.toNMS());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.bossbar.BossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
|
||||
/**
|
||||
* An additional class with additional code for <1.9 servers due to an entity being required
|
||||
*/
|
||||
public class WitherBossBar extends BossBarManagerImpl implements Listener {
|
||||
|
||||
//distance of wither in blocks
|
||||
private static final int WITHER_DISTANCE = 60;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the class
|
||||
* @param plugin - plugin instance
|
||||
*/
|
||||
public WitherBossBar(JavaPlugin plugin) {
|
||||
Preconditions.checkNotNull(plugin, "plugin");
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
|
||||
//when in a small window, it's about 100ms
|
||||
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
|
||||
this, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
super.load();
|
||||
teleport();
|
||||
}
|
||||
|
||||
private void teleport() {
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
|
||||
for (BossBar line : getRegisteredBossBars().values()) {
|
||||
if (!line.containsPlayer(p)) continue;
|
||||
Location loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
|
||||
if (loc.getY() < 1) loc.setY(1);
|
||||
p.sendCustomPacket(new PacketPlayOutEntityTeleport(line.getUniqueId().hashCode(), loc), TabConstants.PacketCategory.BOSSBAR_WITHER_TELEPORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
super.unload();
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning wither as respawn screen destroys all entities in client
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(this, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
|
||||
() -> detectBossBarsAndSend(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
|
||||
}
|
||||
}
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Pose;
|
||||
|
||||
import me.neznamy.tab.api.ArmorStand;
|
||||
import me.neznamy.tab.api.Property;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.protocol.TabPacket;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
|
||||
/**
|
||||
* A class representing an armor stand attached to a player (if the feature is enabled)
|
||||
*/
|
||||
public class BukkitArmorStand implements ArmorStand {
|
||||
|
||||
//entity id counter to pick unique entity IDs
|
||||
private static int idCounter = 2000000000;
|
||||
|
||||
//NameTag feature
|
||||
private final BukkitNameTagX manager = (BukkitNameTagX) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS);
|
||||
|
||||
private final BukkitArmorStandManager asm;
|
||||
|
||||
//owner of the armor stand
|
||||
private final TabPlayer owner;
|
||||
|
||||
//instance of Bukkit player
|
||||
private final Player player;
|
||||
|
||||
//offset in blocks, 0 for original height
|
||||
private double yOffset;
|
||||
|
||||
//entity ID of this armor stand
|
||||
private final int entityId = idCounter++;
|
||||
|
||||
//unique ID of this armor stand
|
||||
private final UUID uuid = UUID.randomUUID();
|
||||
|
||||
//sneaking flag of armor stands
|
||||
private boolean sneaking;
|
||||
|
||||
//armor stand visibility
|
||||
private boolean visible;
|
||||
|
||||
//property dedicated to this armor stand
|
||||
private final Property property;
|
||||
|
||||
//if offset is static or dynamic based on other armor stands
|
||||
private final boolean staticOffset;
|
||||
|
||||
//entity destroy packet
|
||||
private final PacketPlayOutEntityDestroy destroyPacket = new PacketPlayOutEntityDestroy(entityId);
|
||||
|
||||
public BukkitArmorStand(BukkitArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
|
||||
this.asm = asm;
|
||||
this.owner = owner;
|
||||
this.staticOffset = staticOffset;
|
||||
player = (Player) owner.getPlayer();
|
||||
this.yOffset = yOffset;
|
||||
this.property = owner.getProperty(propertyName);
|
||||
visible = getVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
visible = getVisibility();
|
||||
updateMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStaticOffset() {
|
||||
return staticOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getOffset() {
|
||||
return yOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOffset(double offset) {
|
||||
if (yOffset == offset) return;
|
||||
yOffset = offset;
|
||||
for (TabPlayer all : asm.getNearbyPlayers()) {
|
||||
all.sendCustomPacket(getTeleportPacket(all), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_OFFSET_CHANGE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawn(TabPlayer viewer) {
|
||||
for (TabPacket packet : getSpawnPackets(viewer)) {
|
||||
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SPAWN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
for (TabPlayer all : asm.getNearbyPlayers()) all.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(TabPlayer viewer) {
|
||||
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleport() {
|
||||
for (TabPlayer all : asm.getNearbyPlayers()) {
|
||||
all.sendCustomPacket(getTeleportPacket(all), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleport(TabPlayer viewer) {
|
||||
if (!asm.isNearby(viewer) && viewer != owner) {
|
||||
asm.spawn(viewer);
|
||||
} else {
|
||||
viewer.sendCustomPacket(getTeleportPacket(viewer), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sneak(boolean sneaking) {
|
||||
if (this.sneaking == sneaking) return; //idk
|
||||
this.sneaking = sneaking;
|
||||
for (TabPlayer viewer : asm.getNearbyPlayers()) {
|
||||
if (viewer.getVersion().getMinorVersion() == 14 && !manager.isArmorStandsAlwaysVisible()) {
|
||||
//1.14.x client sided bug, de-spawning completely
|
||||
if (sneaking) {
|
||||
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SNEAK);
|
||||
} else {
|
||||
spawn(viewer);
|
||||
}
|
||||
} else {
|
||||
//respawning so there's no animation and it's instant
|
||||
respawn(viewer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVisibility(boolean force) {
|
||||
boolean visibility = getVisibility();
|
||||
if (visible != visibility || force) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns teleport packet for specified viewer
|
||||
* @param viewer - player to get location for
|
||||
* @return teleport packet
|
||||
*/
|
||||
public PacketPlayOutEntityTeleport getTeleportPacket(TabPlayer viewer) {
|
||||
return new PacketPlayOutEntityTeleport(entityId, getArmorStandLocationFor(viewer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates armor stand's metadata
|
||||
*/
|
||||
public void updateMetadata() {
|
||||
for (TabPlayer viewer : asm.getNearbyPlayers()) {
|
||||
viewer.sendCustomPacket(new PacketPlayOutEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer)), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns general visibility rule for everyone with limited info
|
||||
* @return true if armor stand should be visible, false if not
|
||||
*/
|
||||
public boolean getVisibility() {
|
||||
if (manager.isArmorStandsAlwaysVisible()) return true;
|
||||
if (owner.isDisguised() || manager.getVehicleManager().isOnBoat(owner)) return false;
|
||||
return !owner.hasInvisibilityPotion() && owner.getGamemode() != 3 && !manager.hasHiddenNametag(owner) && property.get().length() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns general location where armor stand should be at time of calling
|
||||
* @return Location where armor stand should be for everyone
|
||||
*/
|
||||
public Location getLocation() {
|
||||
double x = player.getLocation().getX();
|
||||
double y = getY() + yOffset + 2;
|
||||
double z = player.getLocation().getZ();
|
||||
if (player.isSleeping()) {
|
||||
y -= 1.76;
|
||||
} else {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
|
||||
y -= (sneaking ? 0.45 : 0.18);
|
||||
} else {
|
||||
y -= (sneaking ? 0.30 : 0.18);
|
||||
}
|
||||
}
|
||||
return new Location(null,x,y,z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Y where player is based on player's vehicle due to bukkit API bug
|
||||
* @return correct player's Y
|
||||
*/
|
||||
protected double getY() {
|
||||
//1.14+ server sided bug
|
||||
Entity vehicle = player.getVehicle();
|
||||
if (vehicle != null) {
|
||||
if (vehicle.getType().toString().contains("HORSE")) { //covering all 3 horse types
|
||||
return vehicle.getLocation().getY() + 0.85;
|
||||
}
|
||||
if (vehicle.getType().toString().equals("DONKEY")) { //1.11+
|
||||
return vehicle.getLocation().getY() + 0.525;
|
||||
}
|
||||
if (vehicle.getType() == EntityType.PIG) {
|
||||
return vehicle.getLocation().getY() + 0.325;
|
||||
}
|
||||
if (vehicle.getType().toString().equals("STRIDER")) { //1.16+
|
||||
return vehicle.getLocation().getY() + 1.15;
|
||||
}
|
||||
}
|
||||
//1.13+ swimming or 1.9+ flying with elytra
|
||||
if (isSwimming() || (TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && player.isGliding())) {
|
||||
return player.getLocation().getY()-1.22;
|
||||
}
|
||||
return player.getLocation().getY();
|
||||
}
|
||||
|
||||
private boolean isSwimming() {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
|
||||
return TAB.getInstance().getServerVersion().getMinorVersion() == 13 && player.isSwimming();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates data watcher with specified display name for viewer
|
||||
* @param displayName - armor stand name
|
||||
* @param viewer - player to apply checks against
|
||||
* @return DataWatcher for viewer
|
||||
*/
|
||||
public DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
|
||||
DataWatcher datawatcher = new DataWatcher();
|
||||
|
||||
byte flag = 32; //invisible
|
||||
if (sneaking) flag += (byte)2;
|
||||
datawatcher.helper().setEntityFlags(flag);
|
||||
datawatcher.helper().setCustomName(displayName, viewer.getVersion());
|
||||
|
||||
boolean visibility;
|
||||
if (isNameVisiblyEmpty(displayName) || !((Player) viewer.getPlayer()).canSee(player) ||
|
||||
manager.hasHiddenNametag(owner, viewer) || manager.hasHiddenNameTagVisibilityView(viewer)) {
|
||||
visibility = false;
|
||||
} else {
|
||||
visibility = visible;
|
||||
}
|
||||
datawatcher.helper().setCustomNameVisible(visibility);
|
||||
|
||||
if (viewer.getVersion().getMinorVersion() > 8 || manager.isMarkerFor18x()) datawatcher.helper().setArmorStandFlags((byte)16);
|
||||
return datawatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if display name is in fact empty, for example only containing color codes
|
||||
* @param displayName - string to check
|
||||
* @return true if it's empty, false if not
|
||||
*/
|
||||
private boolean isNameVisiblyEmpty(String displayName) {
|
||||
if (displayName.length() == 0) return true;
|
||||
if (!displayName.startsWith(EnumChatFormat.COLOR_STRING) && !displayName.startsWith("&") && !displayName.startsWith("#")) return false;
|
||||
String text = IChatBaseComponent.fromColoredText(displayName).toRawText();
|
||||
if (text.contains(" ")) text = text.replace(" ", "");
|
||||
return text.length() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of packets to send to make armor stand spawn with metadata
|
||||
* @param viewer - viewer to apply relational placeholders for
|
||||
* @return List of packets that spawn the armor stand
|
||||
*/
|
||||
public TabPacket[] getSpawnPackets(TabPlayer viewer) {
|
||||
visible = getVisibility();
|
||||
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
|
||||
return new TabPacket[] {
|
||||
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getArmorStandLocationFor(viewer), null),
|
||||
new PacketPlayOutEntityMetadata(entityId, dataWatcher)
|
||||
};
|
||||
} else {
|
||||
return new TabPacket[] {
|
||||
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getArmorStandLocationFor(viewer), dataWatcher),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns location where armor stand should be for specified viewer
|
||||
* @param viewer - player to get location for
|
||||
* @return location of armor stand
|
||||
*/
|
||||
public Location getArmorStandLocationFor(TabPlayer viewer) {
|
||||
return viewer.getVersion().getMinorVersion() == 8 && !manager.isMarkerFor18x() ? getLocation().clone().add(0,-2,0) : getLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void respawn(TabPlayer viewer) {
|
||||
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
|
||||
Runnable spawn = () -> spawn(viewer);
|
||||
if (viewer.getVersion().getMinorVersion() == 8) {
|
||||
//1.8.0 client sided bug
|
||||
TAB.getInstance().getCPUManager().runTaskLater(50, manager, TabConstants.CpuUsageCategory.V1_8_0_BUG_COMPENSATION, spawn);
|
||||
} else {
|
||||
spawn.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import me.neznamy.tab.api.ArmorStand;
|
||||
import me.neznamy.tab.api.ArmorStandManager;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.nametags.unlimited.NameTagX;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A helper class for easy management of armor stands of a player
|
||||
*/
|
||||
public class BukkitArmorStandManager implements ArmorStandManager {
|
||||
|
||||
private final NameTagX nameTagX;
|
||||
//map of registered armor stands
|
||||
private final Map<String, ArmorStand> armorStands = new LinkedHashMap<>();
|
||||
|
||||
//players in entity tracking range
|
||||
private final List<TabPlayer> nearbyPlayers = new ArrayList<>();
|
||||
|
||||
//array to iterate over to avoid concurrent modification and slightly boost performance & memory
|
||||
private ArmorStand[] armorStandArray = new ArmorStand[0];
|
||||
|
||||
private TabPlayer[] nearbyPlayerArray = new TabPlayer[0];
|
||||
|
||||
public BukkitArmorStandManager(NameTagX nameTagX, TabPlayer owner) {
|
||||
this.nameTagX = nameTagX;
|
||||
owner.setProperty(nameTagX, TabConstants.Property.NAMETAG, owner.getProperty(TabConstants.Property.TAGPREFIX).getCurrentRawValue()
|
||||
+ owner.getProperty(TabConstants.Property.CUSTOMTAGNAME).getCurrentRawValue()
|
||||
+ owner.getProperty(TabConstants.Property.TAGSUFFIX).getCurrentRawValue());
|
||||
double height = 0;
|
||||
for (String line : nameTagX.getDynamicLines()) {
|
||||
addArmorStand(line, new BukkitArmorStand(this, owner, line, height, false));
|
||||
height += nameTagX.getSpaceBetweenLines();
|
||||
}
|
||||
for (Map.Entry<String, Object> line : nameTagX.getStaticLines().entrySet()) {
|
||||
addArmorStand(line.getKey(), new BukkitArmorStand(this, owner, line.getKey(), Double.parseDouble(line.getValue().toString()), true));
|
||||
}
|
||||
fixArmorStandHeights();
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports armor stands to player's current location for specified viewer
|
||||
* @param viewer - player to teleport armor stands for
|
||||
*/
|
||||
public void teleport(TabPlayer viewer) {
|
||||
for (ArmorStand a : armorStandArray) a.teleport(viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of nearby players
|
||||
* @return array of nearby players
|
||||
*/
|
||||
public TabPlayer[] getNearbyPlayers(){
|
||||
return nearbyPlayerArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if requested player is nearby, {@code false} if not
|
||||
* @param viewer
|
||||
* Player to check for
|
||||
* @return {@code true} if player nearby, {@code false} if not
|
||||
*/
|
||||
public boolean isNearby(TabPlayer viewer) {
|
||||
return nearbyPlayers.contains(viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if manager contains armor stand with specified entity id, false if not
|
||||
* @param entityId - entity id
|
||||
* @return true if armor stand with specified entity id exists, false if not
|
||||
*/
|
||||
public boolean hasArmorStandWithID(int entityId) {
|
||||
for (ArmorStand a : armorStandArray) {
|
||||
if (a.getEntityId() == entityId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets sneak value of armor stands to specified value
|
||||
* @param sneaking - new sneaking status
|
||||
*/
|
||||
public void sneak(boolean sneaking) {
|
||||
for (ArmorStand a : armorStandArray) a.sneak(sneaking);
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports armor stands to player's current location for all nearby players
|
||||
*/
|
||||
public void teleport() {
|
||||
for (ArmorStand a : armorStandArray) a.teleport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs respawn operation on all armor stands to skip teleport animation
|
||||
*/
|
||||
public void respawn() {
|
||||
for (ArmorStand a : armorStandArray) {
|
||||
for (TabPlayer viewer : nearbyPlayerArray) {
|
||||
a.respawn(viewer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns all armor stands for specified viewer and adds them into nearby players
|
||||
* @param viewer - player to spawn armor stands for
|
||||
*/
|
||||
public void spawn(TabPlayer viewer) {
|
||||
nearbyPlayers.add(viewer);
|
||||
nearbyPlayerArray = nearbyPlayers.toArray(new TabPlayer[0]);
|
||||
if (viewer.getVersion().getMinorVersion() < 8) return;
|
||||
for (ArmorStand a : armorStandArray) a.spawn(viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends destroy packet of all armor stands to specified player and removes them from nearby players list
|
||||
* @param viewer - player to destroy armor stands for
|
||||
*/
|
||||
public void destroy(TabPlayer viewer) {
|
||||
for (ArmorStand a : armorStandArray) a.destroy(viewer);
|
||||
unregisterPlayer(viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes heights of all armor stands due to dynamic lines
|
||||
*/
|
||||
public void fixArmorStandHeights() {
|
||||
double currentY = -nameTagX.getSpaceBetweenLines();
|
||||
for (ArmorStand as : armorStandArray) {
|
||||
if (as.hasStaticOffset()) continue;
|
||||
if (as.getProperty().get().length() != 0) {
|
||||
currentY += nameTagX.getSpaceBetweenLines();
|
||||
as.setOffset(currentY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds armor stand to list and registers it to all nearby players
|
||||
*
|
||||
* @param name
|
||||
* Unique identifier of the text line
|
||||
* @param as
|
||||
* Armor stand to add
|
||||
*/
|
||||
public void addArmorStand(String name, ArmorStand as) {
|
||||
armorStands.put(name, as);
|
||||
armorStandArray = armorStands.values().toArray(new ArmorStand[0]);
|
||||
for (TabPlayer p : nearbyPlayerArray) as.spawn(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets armor stand by name
|
||||
*
|
||||
* @param name
|
||||
* Name to get armor stand by
|
||||
* @return Armor stand with given name or null if not found
|
||||
*/
|
||||
public ArmorStand getArmorStand(String name) {
|
||||
return armorStands.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes armor stand by given name if exists
|
||||
*
|
||||
* @param name
|
||||
* Name of line to remove
|
||||
*/
|
||||
public void removeArmorStand(String name) {
|
||||
if (!armorStands.containsKey(name)) return;
|
||||
armorStands.get(name).destroy();
|
||||
armorStands.remove(name);
|
||||
armorStandArray = armorStands.values().toArray(new ArmorStand[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes specified player from list of nearby players
|
||||
* @param viewer - player to remove
|
||||
*/
|
||||
public void unregisterPlayer(TabPlayer viewer) {
|
||||
if (nearbyPlayers.remove(viewer)) nearbyPlayerArray = nearbyPlayers.toArray(new TabPlayer[0]);
|
||||
}
|
||||
|
||||
public void updateVisibility(boolean force) {
|
||||
for (ArmorStand a : armorStandArray) a.updateVisibility(force);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
for (ArmorStand a : armorStandArray) a.destroy();
|
||||
nearbyPlayers.clear();
|
||||
nearbyPlayerArray = new TabPlayer[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(boolean force) {
|
||||
boolean fix = false;
|
||||
for (ArmorStand as : armorStandArray) {
|
||||
if (as.getProperty().update() || force) {
|
||||
as.refresh();
|
||||
fix = true;
|
||||
}
|
||||
}
|
||||
if (fix) fixArmorStandHeights();
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.nametags.unlimited.NameTagX;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* The core class for unlimited NameTag mode
|
||||
*/
|
||||
public class BukkitNameTagX extends NameTagX {
|
||||
|
||||
//bukkit event listener
|
||||
private final EventListener eventListener = new EventListener(this);
|
||||
|
||||
private final VehicleRefresher vehicleManager = new VehicleRefresher(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
* @param plugin - plugin instance
|
||||
*/
|
||||
public BukkitNameTagX(JavaPlugin plugin) {
|
||||
super(BukkitArmorStandManager::new);
|
||||
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
|
||||
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_PACKET_LISTENER, new PacketListener(this));
|
||||
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_VEHICLE_REFRESHER, vehicleManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
super.load();
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (isPlayerDisabled(all)) continue;
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
spawnArmorStands(viewer, all);
|
||||
}
|
||||
}
|
||||
startVisibilityRefreshTask();
|
||||
}
|
||||
|
||||
private void startVisibilityRefreshTask() {
|
||||
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(500, this, TabConstants.CpuUsageCategory.REFRESHING_NAME_TAG_VISIBILITY, () -> {
|
||||
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (isPlayerDisabled(p)) continue;
|
||||
getArmorStandManager(p).updateVisibility(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
super.unload();
|
||||
HandlerList.unregisterAll(eventListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onJoin(TabPlayer connectedPlayer) {
|
||||
super.onJoin(connectedPlayer);
|
||||
if (isPlayerDisabled(connectedPlayer)) return;
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
spawnArmorStands(viewer, connectedPlayer);
|
||||
spawnArmorStands(connectedPlayer, viewer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnBoat(TabPlayer player) {
|
||||
return vehicleManager != null && vehicleManager.isOnBoat(player);
|
||||
}
|
||||
|
||||
private void spawnArmorStands(TabPlayer viewer, TabPlayer target) {
|
||||
if (target == viewer || isPlayerDisabled(target)) return;
|
||||
if (((Player) viewer.getPlayer()).getWorld() != ((Player) target.getPlayer()).getWorld()) return;
|
||||
if (getDistance(viewer, target) <= 48) {
|
||||
if (((Player)viewer.getPlayer()).canSee((Player)target.getPlayer()) && !target.isVanished()) getArmorStandManager(target).spawn(viewer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNameTagPreview(TabPlayer player, boolean status) {
|
||||
if (status) {
|
||||
getArmorStandManager(player).spawn(player);
|
||||
} else {
|
||||
getArmorStandManager(player).destroy(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeArmorStands(TabPlayer player) {
|
||||
if (isPlayerDisabled(player)) return;
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
spawnArmorStands(viewer, player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pauseArmorStands(TabPlayer player) {
|
||||
if (isPlayerDisabled(player)) return;
|
||||
getArmorStandManager(player).destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateNameTagVisibilityView(TabPlayer player) {
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
getArmorStandManager(all).updateVisibility(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQuit(TabPlayer disconnectedPlayer) {
|
||||
super.onQuit(disconnectedPlayer);
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
getArmorStandManager(all).unregisterPlayer(disconnectedPlayer);
|
||||
}
|
||||
getArmorStandManager(disconnectedPlayer).destroy();
|
||||
TAB.getInstance().getCPUManager().runTaskLater(500, this, TabConstants.CpuUsageCategory.PLAYER_QUIT, () -> getArmorStandManager(disconnectedPlayer).destroy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWorldChange(TabPlayer p, String from, String to) {
|
||||
super.onWorldChange(p, from, to);
|
||||
if (isUnlimitedDisabled(p.getServer(), to)) {
|
||||
getDisabledUnlimitedPlayers().add(p);
|
||||
updateTeamData(p);
|
||||
} else if (getDisabledUnlimitedPlayers().remove(p)) {
|
||||
updateTeamData(p);
|
||||
}
|
||||
if (isPreviewingNametag(p)) {
|
||||
getArmorStandManager(p).spawn(p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns flat distance between two players ignoring Y value
|
||||
* @param player1 - first player
|
||||
* @param player2 - second player
|
||||
* @return flat distance in blocks
|
||||
*/
|
||||
private double getDistance(TabPlayer player1, TabPlayer player2) {
|
||||
Location loc1 = ((Player) player1.getPlayer()).getLocation();
|
||||
Location loc2 = ((Player) player2.getPlayer()).getLocation();
|
||||
return Math.sqrt(Math.pow(loc1.getX()-loc2.getX(), 2) + Math.pow(loc1.getZ()-loc2.getZ(), 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitArmorStandManager getArmorStandManager(TabPlayer player) {
|
||||
return (BukkitArmorStandManager) armorStandManagerMap.get(player);
|
||||
}
|
||||
|
||||
public VehicleRefresher getVehicleManager() {
|
||||
return vehicleManager;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
|
||||
/**
|
||||
* The event listener part for securing proper functionality of armor stands
|
||||
*/
|
||||
public class EventListener implements Listener {
|
||||
|
||||
//the NameTag feature handler
|
||||
private final BukkitNameTagX feature;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param feature - NameTag feature handler
|
||||
*/
|
||||
public EventListener(BukkitNameTagX feature) {
|
||||
this.feature = feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sneak event listener to de-spawn and spawn armor stands to skip animation
|
||||
* @param e - sneak event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onSneak(PlayerToggleSneakEvent e) {
|
||||
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (p == null || feature.isPlayerDisabled(p)) return;
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning armor stands as respawn screen destroys all entities in client
|
||||
* @param e - respawn event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
|
||||
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (feature.isPlayerDisabled(respawned)) return;
|
||||
feature.getArmorStandManager(respawned).teleport();
|
||||
});
|
||||
}
|
||||
}
|
||||
Loaded 100 of 486 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user