mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-13 12:42:06 -05:00
Compare commits
2193
Commits
2.8.11-pre13
...
4.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e11113073f | ||
|
|
3615818812 | ||
|
|
0cdef12caf | ||
|
|
dda8c6c053 | ||
|
|
8c3327f553 | ||
|
|
c037a14e1f | ||
|
|
bd661360ee | ||
|
|
317fabbc87 | ||
|
|
87161446c5 | ||
|
|
7db8b2e84b | ||
|
|
5f3fa2b317 | ||
|
|
64c15730a6 | ||
|
|
e5ab46b8ad | ||
|
|
3060d8cc2b | ||
|
|
f5d6ba462e | ||
|
|
81fdeece96 | ||
|
|
8f60439ef3 | ||
|
|
c5420ead90 | ||
|
|
99d372d03c | ||
|
|
1219a9822f | ||
|
|
d40c182af9 | ||
|
|
785e986a72 | ||
|
|
e8b80f455b | ||
|
|
2d545f1bf1 | ||
|
|
d4713dc7f4 | ||
|
|
cd96c13670 | ||
|
|
808607faf0 | ||
|
|
bc403f5c23 | ||
|
|
bfc67e1953 | ||
|
|
a186c16c34 | ||
|
|
f9be9d4c4b | ||
|
|
79fc3ce33f | ||
|
|
055ff8749e | ||
|
|
d36410e851 | ||
|
|
0fed5dca7f | ||
|
|
dd34ffc5b2 | ||
|
|
2cd5ac85f9 | ||
|
|
571f949f50 | ||
|
|
81e32957b2 | ||
|
|
4639016ffb | ||
|
|
dabc2b49dc | ||
|
|
2dec389c8c | ||
|
|
bfe5b5960e | ||
|
|
e352967a17 | ||
|
|
930917f285 | ||
|
|
b9747f7fbe | ||
|
|
5ac9dcec37 | ||
|
|
8328b43783 | ||
|
|
131fcfe144 | ||
|
|
709cb967b5 | ||
|
|
3370b83087 | ||
|
|
5c44bd500c | ||
|
|
ab8a345944 | ||
|
|
337dd56c87 | ||
|
|
de78ae30db | ||
|
|
3f64185389 | ||
|
|
5077cbbc9a | ||
|
|
4d522ba5f5 | ||
|
|
4f1180b3f5 | ||
|
|
577a3f2551 | ||
|
|
8623ab053e | ||
|
|
6d0f99eaf1 | ||
|
|
f9490d2652 | ||
|
|
9d321e4a31 | ||
|
|
b8c2625b80 | ||
|
|
8e42f328dc | ||
|
|
0c6b4efba5 | ||
|
|
0821ed4dae | ||
|
|
8bc38e25f9 | ||
|
|
7a7a4e6da2 | ||
|
|
f7d703ed1e | ||
|
|
443d7fd082 | ||
|
|
b6114625c1 | ||
|
|
4a3eb78f12 | ||
|
|
fa117a671a | ||
|
|
6448fb975a | ||
|
|
1c685ce0bb | ||
|
|
1662fa8d60 | ||
|
|
2787b1735b | ||
|
|
e58390e30b | ||
|
|
41a10a61bf | ||
|
|
fdcc7d5e7c | ||
|
|
50b39e462f | ||
|
|
4bc781f0da | ||
|
|
aca4c00083 | ||
|
|
77b820ad4d | ||
|
|
328f604236 | ||
|
|
2f77bf3d10 | ||
|
|
23eb55aa06 | ||
|
|
32b7356bc0 | ||
|
|
6091641ed9 | ||
|
|
e368213d18 | ||
|
|
58f9bb6fca | ||
|
|
79798199b8 | ||
|
|
c8d20f8479 | ||
|
|
c0498a60b4 | ||
|
|
faa01ddfc2 | ||
|
|
a2f628e267 | ||
|
|
66007b9121 | ||
|
|
9e0056b603 | ||
|
|
b15cc3a6af | ||
|
|
5393f869e7 | ||
|
|
f8088402e8 | ||
|
|
979ff7d2b8 | ||
|
|
bdbde58f42 | ||
|
|
1e26e0bd13 | ||
|
|
be3c00d17e | ||
|
|
d542bb67de | ||
|
|
a79cdfaa1c | ||
|
|
42db715421 | ||
|
|
2228d5a35d | ||
|
|
c434d0eafd | ||
|
|
414a3a971f | ||
|
|
b8a22a2561 | ||
|
|
bc1dc2f84f | ||
|
|
904c4f27aa | ||
|
|
b19e5435ef | ||
|
|
78fbcd829a | ||
|
|
a255844a92 | ||
|
|
c7a548c575 | ||
|
|
90a4bab114 | ||
|
|
a1633d146a | ||
|
|
87bf4b03df | ||
|
|
7731aea0d6 | ||
|
|
e66350111e | ||
|
|
7e5df68626 | ||
|
|
8e3e7de4ee | ||
|
|
0ee535fbd5 | ||
|
|
1b471d266c | ||
|
|
19a4f6ea1f | ||
|
|
7cc8d71224 | ||
|
|
b843475a0a | ||
|
|
027c0fa771 | ||
|
|
c08e76f110 | ||
|
|
b837fe7d15 | ||
|
|
d4d1bed408 | ||
|
|
8870295d54 | ||
|
|
79cd6e4ae2 | ||
|
|
8e5ea29c40 | ||
|
|
287b51a0bf | ||
|
|
28cbf952fa | ||
|
|
598c35a3aa | ||
|
|
b8b67a64ab | ||
|
|
7f6020ef34 | ||
|
|
aeed453e15 | ||
|
|
71dddcca4c | ||
|
|
46cdf79e20 | ||
|
|
d7af96823b | ||
|
|
5a228bd6b3 | ||
|
|
fcd95655ce | ||
|
|
7f5b57f8d2 | ||
|
|
f596877716 | ||
|
|
1802c8fd90 | ||
|
|
e05856facc | ||
|
|
45cfc3abb0 | ||
|
|
2d39579861 | ||
|
|
431f4bb4d5 | ||
|
|
7bc9d38176 | ||
|
|
7536447a8f | ||
|
|
059b796061 | ||
|
|
a89942484f | ||
|
|
c28be3fd65 | ||
|
|
5f1116795c | ||
|
|
4f54d2f6b2 | ||
|
|
6b7bedbe2d | ||
|
|
20ff225c31 | ||
|
|
af2700798e | ||
|
|
f9c280bbdc | ||
|
|
6ee31c2c3f | ||
|
|
cbd944f6f2 | ||
|
|
cc20748f31 | ||
|
|
c9545165aa | ||
|
|
05c10bf2e7 | ||
|
|
67aeae2094 | ||
|
|
1cc85a0647 | ||
|
|
988c0f8b4c | ||
|
|
62306fa5d6 | ||
|
|
641c5a49c3 | ||
|
|
5637402435 | ||
|
|
728d6c9a25 | ||
|
|
22d3ba2877 | ||
|
|
d1e032e010 | ||
|
|
80c4316826 | ||
|
|
3addeba256 | ||
|
|
c52db733b7 | ||
|
|
07a492e353 | ||
|
|
3c1ec478c2 | ||
|
|
f3f614c3de | ||
|
|
a6627ac6c6 | ||
|
|
8d1040518f | ||
|
|
ca5d17e6ec | ||
|
|
b9fab42510 | ||
|
|
22f1ef43c4 | ||
|
|
ffbd4bf26b | ||
|
|
4d6f93e975 | ||
|
|
cb45cff77f | ||
|
|
4718040345 | ||
|
|
afffefcce1 | ||
|
|
4580f47563 | ||
|
|
e51f4bb3a1 | ||
|
|
e8c8d1dee7 | ||
|
|
3559afaef7 | ||
|
|
fc29420eb3 | ||
|
|
023cd3fb75 | ||
|
|
2781d9f232 | ||
|
|
d31e5de449 | ||
|
|
245fcdf917 | ||
|
|
e3ef3ffa68 | ||
|
|
95c3608c3f | ||
|
|
7c2236dc96 | ||
|
|
b83ef0e352 | ||
|
|
2865871d04 | ||
|
|
7f7b76a11e | ||
|
|
52569e4c65 | ||
|
|
0a0ae6c83b | ||
|
|
6c8a41388e | ||
|
|
fe94e33d85 | ||
|
|
ca1be457cf | ||
|
|
550b6350ea | ||
|
|
76470c880d | ||
|
|
a315bd88b3 | ||
|
|
bb62930525 | ||
|
|
b7490f0002 | ||
|
|
42f3851339 | ||
|
|
34f39ac8d5 | ||
|
|
8c16330119 | ||
|
|
8323c21c3e | ||
|
|
2a3ca932a4 | ||
|
|
e339928caa | ||
|
|
3eeb025f23 | ||
|
|
f19c1f1baa | ||
|
|
003e6a6adb | ||
|
|
95d435d61c | ||
|
|
bbc4e1d767 | ||
|
|
ae46adf8b9 | ||
|
|
461ef988d3 | ||
|
|
307d21cc85 | ||
|
|
e2beac6570 | ||
|
|
75e455f078 | ||
|
|
19c832d370 | ||
|
|
9cd4afe1d4 | ||
|
|
3b00f07f78 | ||
|
|
0b5008c087 | ||
|
|
76f210db03 | ||
|
|
85c8d9f1b9 | ||
|
|
a795fc7f77 | ||
|
|
7742ab8220 | ||
|
|
3e235a8f18 | ||
|
|
30f9888f6a | ||
|
|
d1f8886e2b | ||
|
|
3c037f9aa6 | ||
|
|
c70346c57c | ||
|
|
bd40dd85af | ||
|
|
eacd929b0f | ||
|
|
0ff5f17b7d | ||
|
|
57fa01151e | ||
|
|
e7bc1fc74b | ||
|
|
897f35ab53 | ||
|
|
7f8b738a69 | ||
|
|
079302fb05 | ||
|
|
630fcc9e50 | ||
|
|
c1601ca487 | ||
|
|
d035b9cb80 | ||
|
|
8db5e4d8ba | ||
|
|
4d51bb39d7 | ||
|
|
7eb0231e08 | ||
|
|
6412461576 | ||
|
|
5732eb366b | ||
|
|
ed3df8bb68 | ||
|
|
787f531203 | ||
|
|
cf5d888901 | ||
|
|
1969e5f755 | ||
|
|
8d12ee0958 | ||
|
|
c3bbc89c5b | ||
|
|
625c7cf3c2 | ||
|
|
14f0321992 | ||
|
|
e5294c596a | ||
|
|
c83290a99a | ||
|
|
8c66310af0 | ||
|
|
5ae91bf89e | ||
|
|
d5dc1b7bdb | ||
|
|
864eee3349 | ||
|
|
88932e3a5e | ||
|
|
4ebc60ff00 | ||
|
|
b0cb63b05d | ||
|
|
1db40ee2d5 | ||
|
|
fd3f0ebd21 | ||
|
|
ce73069345 | ||
|
|
088f2748e3 | ||
|
|
d9a8c69f43 | ||
|
|
16ff503b6a | ||
|
|
68c78b9c73 | ||
|
|
0c9d78a644 | ||
|
|
99d22642e2 | ||
|
|
dc762eacf8 | ||
|
|
5a44f30140 | ||
|
|
82e86b0f8a | ||
|
|
9c4eb4359c | ||
|
|
827c380463 | ||
|
|
7c4fc324c8 | ||
|
|
46215f3304 | ||
|
|
1accc21365 | ||
|
|
b74c2f62f7 | ||
|
|
9d14c0c923 | ||
|
|
650fda5781 | ||
|
|
286ccf5836 | ||
|
|
dfa310666c | ||
|
|
06595241ab | ||
|
|
0f80dd1cfd | ||
|
|
7bc5be264c | ||
|
|
e31784fb1e | ||
|
|
ae156f424b | ||
|
|
cfe0b3883e | ||
|
|
fc6beaa0a5 | ||
|
|
5f2140b147 | ||
|
|
c497bdff39 | ||
|
|
7c84b6432e | ||
|
|
3f787c3b0b | ||
|
|
2046a39e99 | ||
|
|
2ccf89e396 | ||
|
|
efaebc3cb0 | ||
|
|
d7c952cc36 | ||
|
|
1d3ea37129 | ||
|
|
1e9c47398d | ||
|
|
5a1f48f1a5 | ||
|
|
ed65554d30 | ||
|
|
0f19970401 | ||
|
|
b419499710 | ||
|
|
14a5f30b74 | ||
|
|
302cdf200f | ||
|
|
bdde257cac | ||
|
|
73af013bca | ||
|
|
e421427007 | ||
|
|
169a180d68 | ||
|
|
16a19bf578 | ||
|
|
5b39204a8a | ||
|
|
b2bb125248 | ||
|
|
980354104b | ||
|
|
b27571dcaa | ||
|
|
b215bf9000 | ||
|
|
ab75b13b3f | ||
|
|
31cd2aeb08 | ||
|
|
8b6b33b314 | ||
|
|
1b66257ece | ||
|
|
bed9fcf212 | ||
|
|
692f309750 | ||
|
|
2c9c9b502a | ||
|
|
3da8fd2a77 | ||
|
|
ba6826f3d1 | ||
|
|
68b3629437 | ||
|
|
8bc5962d1c | ||
|
|
b8aa1982e4 | ||
|
|
e0a427732e | ||
|
|
8d1f60f5fa | ||
|
|
5cd7fdf385 | ||
|
|
e54d342ac5 | ||
|
|
9019261c2a | ||
|
|
b1a5092053 | ||
|
|
ef8bdbb40f | ||
|
|
0d67826b3d | ||
|
|
6c99e28d51 | ||
|
|
13931aafa5 | ||
|
|
089e7ce8b5 | ||
|
|
54287c8a29 | ||
|
|
5ebc5dee21 | ||
|
|
bedbdbcca9 | ||
|
|
a1c35adf4c | ||
|
|
81b6a9be8b | ||
|
|
88eee58329 | ||
|
|
2f02a5bda4 | ||
|
|
ba716df634 | ||
|
|
988292b82a | ||
|
|
d52cfdd6e6 | ||
|
|
b13a49539f | ||
|
|
32a39a361c | ||
|
|
0e2e9b9ae6 | ||
|
|
331faefde2 | ||
|
|
1603ebe9fc | ||
|
|
bad4359469 | ||
|
|
a860b463ed | ||
|
|
b3b0a96bb8 | ||
|
|
c211724d13 | ||
|
|
0fb9a832ca | ||
|
|
7ce1a97c93 | ||
|
|
1d461c1adf | ||
|
|
5ec09c37bc | ||
|
|
95a20766c3 | ||
|
|
87feb1c2bf | ||
|
|
8c732ac7e9 | ||
|
|
977c1f4d74 | ||
|
|
65ac22aadb | ||
|
|
8e7a14bdb8 | ||
|
|
4693a297b6 | ||
|
|
af69dcd9b5 | ||
|
|
71110fc722 | ||
|
|
c2af5632a2 | ||
|
|
399e813ca4 | ||
|
|
daac7c6171 | ||
|
|
940352ef2c | ||
|
|
c70b909c0f | ||
|
|
dcc2eb9217 | ||
|
|
5425b3b6ea | ||
|
|
a90ea3e3fa | ||
|
|
6d52b7b615 | ||
|
|
51635a7322 | ||
|
|
481b33ba55 | ||
|
|
22c785fc99 | ||
|
|
42d4159b69 | ||
|
|
6d95a2f06d | ||
|
|
7f0b1d8a2e | ||
|
|
a3fecd0fea | ||
|
|
f3d6a54a2a | ||
|
|
1d434f2557 | ||
|
|
b3500086e4 | ||
|
|
bf9202af38 | ||
|
|
bd2c683568 | ||
|
|
c03feaab23 | ||
|
|
9cb9ccbd2a | ||
|
|
4606ebd588 | ||
|
|
679be83c5a | ||
|
|
afe868bb95 | ||
|
|
c9ce3aa23e | ||
|
|
11a49c4874 | ||
|
|
65d2a0fba9 | ||
|
|
c1219195c7 | ||
|
|
fca0dfc91b | ||
|
|
7f1cc3063c | ||
|
|
e444c25287 | ||
|
|
476354e407 | ||
|
|
9f84d3f22f | ||
|
|
4608ff360c | ||
|
|
68b12e199d | ||
|
|
4b1151f769 | ||
|
|
6687214f2a | ||
|
|
a57757258e | ||
|
|
3a6060c45d | ||
|
|
ee375c1b60 | ||
|
|
c9d870cca0 | ||
|
|
3ad8015cc1 | ||
|
|
33609c322f | ||
|
|
431b89c487 | ||
|
|
35a146992c | ||
|
|
dedee114c4 | ||
|
|
9d42af0388 | ||
|
|
8f5d81bc33 | ||
|
|
b17f5563dd | ||
|
|
0ab8bb363a | ||
|
|
ac5fca86bd | ||
|
|
28c2c2e193 | ||
|
|
904c0b6a17 | ||
|
|
f3c31bc92c | ||
|
|
6016b84889 | ||
|
|
cfb5cb3a9f | ||
|
|
1746f6b1d0 | ||
|
|
9c3979a0d9 | ||
|
|
810676c69f | ||
|
|
d0892a245a | ||
|
|
f539b1b93b | ||
|
|
adfa64a29f | ||
|
|
d4f86ee79d | ||
|
|
6ebe31b737 | ||
|
|
dcbd5e62d4 | ||
|
|
a7e6e49363 | ||
|
|
d115257ff7 | ||
|
|
d578be276c | ||
|
|
e338f57441 | ||
|
|
c87321b5b0 | ||
|
|
84bb9b449b | ||
|
|
9d613fc24d | ||
|
|
3f2ae3131e | ||
|
|
0e9f53bd58 | ||
|
|
3981815b63 | ||
|
|
3ef99e4e7c | ||
|
|
76767a0958 | ||
|
|
1683943d58 | ||
|
|
cc0dbe8623 | ||
|
|
b056251e07 | ||
|
|
3b352c66e2 | ||
|
|
b4f0ea73c1 | ||
|
|
1c683788b5 | ||
|
|
250597df2d | ||
|
|
0d2d1d5aaa | ||
|
|
061375d1e7 | ||
|
|
5b05614305 | ||
|
|
69db2e0e53 | ||
|
|
00ef364324 | ||
|
|
de68312b6b | ||
|
|
ddac08bcbc | ||
|
|
517e285bb3 | ||
|
|
81a08a6b51 | ||
|
|
4dfef1d032 | ||
|
|
5d38e28cae | ||
|
|
66a12d10c4 | ||
|
|
1a4027c8d5 | ||
|
|
2a0243c51a | ||
|
|
4851d09e70 | ||
|
|
3cdf3accfd | ||
|
|
fd1f094216 | ||
|
|
25ece1ecb8 | ||
|
|
802e65c280 | ||
|
|
882688f056 | ||
|
|
97d60d0ab3 | ||
|
|
9ef99daed1 | ||
|
|
71c85d9d39 | ||
|
|
b981ab46be | ||
|
|
016633e494 | ||
|
|
1db862ab56 | ||
|
|
3563786310 | ||
|
|
ac8851dd6a | ||
|
|
53ab2d9793 | ||
|
|
2a58c86ffc | ||
|
|
fa46d527ae | ||
|
|
667bd38305 | ||
|
|
faba439466 | ||
|
|
c301157758 | ||
|
|
8d09d425a4 | ||
|
|
11f03d5a10 | ||
|
|
83625202a1 | ||
|
|
eb4b715a88 | ||
|
|
89dce941cf | ||
|
|
6371b67e9e | ||
|
|
966e27418a | ||
|
|
4adf7d1734 | ||
|
|
1a4c4a6fe8 | ||
|
|
7ff36592ae | ||
|
|
1b513412d2 | ||
|
|
f2bb43fbcd | ||
|
|
df5d07d885 | ||
|
|
0ad2f33c4c | ||
|
|
dee48fccb3 | ||
|
|
9ca372ab13 | ||
|
|
58a849a902 | ||
|
|
895072ec35 | ||
|
|
2b2c4d2c39 | ||
|
|
d534b6e2e8 | ||
|
|
783e9d9931 | ||
|
|
a109d9b55b | ||
|
|
5ce59ce44f | ||
|
|
1e9195a9bc | ||
|
|
3099d16468 | ||
|
|
c9c6af346c | ||
|
|
af80a43628 | ||
|
|
dc3c98ec91 | ||
|
|
dffa2c83f6 | ||
|
|
dc66f253a7 | ||
|
|
17498cbfc2 | ||
|
|
96e4d30168 | ||
|
|
c9d1934bb9 | ||
|
|
8bec083194 | ||
|
|
abf047be23 | ||
|
|
4a95399d56 | ||
|
|
885bf5154e | ||
|
|
3e146fc496 | ||
|
|
95710bcaf5 | ||
|
|
edd620f17d | ||
|
|
9c466d8b1e | ||
|
|
b385ab70fb | ||
|
|
140224840b | ||
|
|
2078cd5a5f | ||
|
|
27bae7fdbd | ||
|
|
174b4214de | ||
|
|
4ac524840c | ||
|
|
dd4ce4e2c3 | ||
|
|
2a03d69628 | ||
|
|
c28252e6ce | ||
|
|
da1768d833 | ||
|
|
b58c3e6b47 | ||
|
|
c7575bdc8f | ||
|
|
e275d22061 | ||
|
|
b5600ef22a | ||
|
|
b8e14c9256 | ||
|
|
1af5b9a308 | ||
|
|
2dd719c3a5 | ||
|
|
45d94694cd | ||
|
|
3ce8c6a192 | ||
|
|
7deb86f105 | ||
|
|
d9b4f6a43f | ||
|
|
54a7fb8721 | ||
|
|
d1dcb2f437 | ||
|
|
a92bab617a | ||
|
|
57e34bc595 | ||
|
|
953baa273c | ||
|
|
c403cb3187 | ||
|
|
ffd6dc5bb9 | ||
|
|
0f6504a3dc | ||
|
|
f376fea598 | ||
|
|
4896cdff91 | ||
|
|
b4c87431bc | ||
|
|
1cffbab97d | ||
|
|
9b0711075a | ||
|
|
17a8e902eb | ||
|
|
4bdb5ba245 | ||
|
|
fd7748d280 | ||
|
|
317b0292ff | ||
|
|
b87be649bc | ||
|
|
e2dda54011 | ||
|
|
d17518c39b | ||
|
|
48541dd0bd | ||
|
|
b83b023fee | ||
|
|
60e0c78033 | ||
|
|
a395f7f01f | ||
|
|
09c36ef69e | ||
|
|
9d5fa685a0 | ||
|
|
6794f593fa | ||
|
|
5e9965276f | ||
|
|
1473038955 | ||
|
|
2041ca57f3 | ||
|
|
098782d820 | ||
|
|
87e9a6defd | ||
|
|
3d896ff828 | ||
|
|
bf93240888 | ||
|
|
0b0e30c3c9 | ||
|
|
84d3816bd1 | ||
|
|
04ff606947 | ||
|
|
1cb8e6e238 | ||
|
|
4a2226170d | ||
|
|
1469124cfc | ||
|
|
0826095e73 | ||
|
|
1bd698adde | ||
|
|
1058d269ff | ||
|
|
bacd87b78e | ||
|
|
5befbe8780 | ||
|
|
e159b2adae | ||
|
|
52f7418853 | ||
|
|
76061f7ccd | ||
|
|
7d39e21eae | ||
|
|
fee3723474 | ||
|
|
2f0e4c6888 | ||
|
|
b04cb6d3c5 | ||
|
|
a9228a698a | ||
|
|
27102495d8 | ||
|
|
d1bf96ab33 | ||
|
|
3360dbb468 | ||
|
|
d224e7584e | ||
|
|
6373540076 | ||
|
|
5f78aca997 | ||
|
|
145d944d33 | ||
|
|
ac91940e3b | ||
|
|
ef8452be73 | ||
|
|
47e0810697 | ||
|
|
1f30f225c7 | ||
|
|
fcbfd60ddb | ||
|
|
46f9033713 | ||
|
|
7e65018a0e | ||
|
|
9ef3485968 | ||
|
|
257f8f1145 | ||
|
|
8d65cbb0e5 | ||
|
|
20d221e3ca | ||
|
|
94072acf10 | ||
|
|
0422cc2597 | ||
|
|
58f61e645b | ||
|
|
5f416bd9e5 | ||
|
|
64528c913c | ||
|
|
357e7c6a49 | ||
|
|
81dd202053 | ||
|
|
189797aa73 | ||
|
|
1ae2ef7cad | ||
|
|
de14e14d33 | ||
|
|
19892c14c1 | ||
|
|
2e00c4c002 | ||
|
|
97aa367629 | ||
|
|
87cc9a030b | ||
|
|
8ba83d1cb1 | ||
|
|
9f0e60bd47 | ||
|
|
93b2a25b2e | ||
|
|
4242d7a839 | ||
|
|
0ddb99bd46 | ||
|
|
969672e3ad | ||
|
|
2423fbb150 | ||
|
|
a99928dbf5 | ||
|
|
12f5dd52b4 | ||
|
|
2bd105446e | ||
|
|
6d85875fde | ||
|
|
42810aa490 | ||
|
|
bd31521bd3 | ||
|
|
ec0deb8378 | ||
|
|
ae66b6e9a1 | ||
|
|
3b86becebd | ||
|
|
4a8d86825b | ||
|
|
54a3bd81b4 | ||
|
|
b6768cca99 | ||
|
|
60c414deed | ||
|
|
009283a83f | ||
|
|
4a430b97df | ||
|
|
722333da2e | ||
|
|
1215af5262 | ||
|
|
254088c37b | ||
|
|
cb2ce4acca | ||
|
|
872adca8fd | ||
|
|
f37cc0bdd8 | ||
|
|
4063578ac3 | ||
|
|
2528de7eeb | ||
|
|
37caed3123 | ||
|
|
27fe9dfb12 | ||
|
|
0169cbd4d2 | ||
|
|
ed760aec08 | ||
|
|
858cbcefeb | ||
|
|
8a87d581a9 | ||
|
|
6d642f4e51 | ||
|
|
aca63640bc | ||
|
|
1845a35315 | ||
|
|
cab5f89a95 | ||
|
|
1777d8bf01 | ||
|
|
91b0bfaa9d | ||
|
|
f1cf994d10 | ||
|
|
69957be933 | ||
|
|
b78eb5efb6 | ||
|
|
3577ad1964 | ||
|
|
4510491177 | ||
|
|
01b3a12377 | ||
|
|
64ae9863fb | ||
|
|
ecd315a407 | ||
|
|
678eed288e | ||
|
|
9510455cb1 | ||
|
|
e925ce23d0 | ||
|
|
c68cad6a1e | ||
|
|
01d6eda4fb | ||
|
|
73e7119bbc | ||
|
|
cea781382a | ||
|
|
cf25b84e86 | ||
|
|
86e1d22390 | ||
|
|
1625be111f | ||
|
|
00cda3b2aa | ||
|
|
85a00a4436 | ||
|
|
37aec1a497 | ||
|
|
ef637616ee | ||
|
|
af3b5067fb | ||
|
|
53e5b15f96 | ||
|
|
6a3afae607 | ||
|
|
2e05c7d9d1 | ||
|
|
fb698a04b7 | ||
|
|
52bdf30ddb | ||
|
|
a92f07aa40 | ||
|
|
003ff7f99d | ||
|
|
47b9806ae7 | ||
|
|
d2a7c65e08 | ||
|
|
e232336a5f | ||
|
|
8816c75212 | ||
|
|
f9d54751ba | ||
|
|
43d41ee761 | ||
|
|
4512ba8d5e | ||
|
|
8b55bca564 | ||
|
|
d8d54970d3 | ||
|
|
15ea4482f9 | ||
|
|
87f9f38eac | ||
|
|
dba559f041 | ||
|
|
c610b6f323 | ||
|
|
a5fb900e19 | ||
|
|
09d6e455f5 | ||
|
|
e68097b0f4 | ||
|
|
0744fed953 | ||
|
|
de0adf4067 | ||
|
|
76bf2b3efc | ||
|
|
c089badeb4 | ||
|
|
b3b99d366a | ||
|
|
4a36486f65 | ||
|
|
75126d00d9 | ||
|
|
17ebf95d5b | ||
|
|
c764420cfe | ||
|
|
28f05fc2af | ||
|
|
a420892887 | ||
|
|
d910dc5913 | ||
|
|
c9c1cbc349 | ||
|
|
fd4704f75e | ||
|
|
5400ca637f | ||
|
|
79d2610117 | ||
|
|
6a51f4cb9d | ||
|
|
c190776665 | ||
|
|
3765a1232e | ||
|
|
28866eba21 | ||
|
|
08adee3dc3 | ||
|
|
8a1adaf2cd | ||
|
|
fdb9accaa6 | ||
|
|
4f7656748b | ||
|
|
03e4495ac7 | ||
|
|
01af5cb9e1 | ||
|
|
8d49f32df2 | ||
|
|
8a955b3bc8 | ||
|
|
ead40cb413 | ||
|
|
5e07f78d4f | ||
|
|
c7d98795bc | ||
|
|
9f637e3d6a | ||
|
|
2d5f074c78 | ||
|
|
3be6ba484d | ||
|
|
d9d74e7a32 | ||
|
|
887306a5ed | ||
|
|
4b2542eb4f | ||
|
|
66bd3baa3e | ||
|
|
bf32004f8f | ||
|
|
bfb2df9c95 | ||
|
|
ddeb05e408 | ||
|
|
9312d61258 | ||
|
|
d44909bc42 | ||
|
|
1d77df335c | ||
|
|
a268dc25f4 | ||
|
|
8c5696faa0 | ||
|
|
94f3ccfb7d | ||
|
|
576329214d | ||
|
|
5c13c2e225 | ||
|
|
1070f85617 | ||
|
|
7c9dc87fcb | ||
|
|
363e58bf91 | ||
|
|
daca3f5b71 | ||
|
|
a7d2ec084b | ||
|
|
167f8cd0ff | ||
|
|
170307f45f | ||
|
|
2f36377780 | ||
|
|
48c565d85d | ||
|
|
86b3cc4044 | ||
|
|
6a2d5091b8 | ||
|
|
dc09e77190 | ||
|
|
4fc139c0c6 | ||
|
|
e5d800f469 | ||
|
|
4879ac28b5 | ||
|
|
bd0badab48 | ||
|
|
5575a26d3b | ||
|
|
d8cbe96949 | ||
|
|
cfb506a7ff | ||
|
|
d5b0ff7f40 | ||
|
|
31d259210c | ||
|
|
39112b8d41 | ||
|
|
4c92286ebb | ||
|
|
a317aeba85 | ||
|
|
dcaf17294e | ||
|
|
0bc734689a | ||
|
|
d0c2c00cc9 | ||
|
|
fd02328af9 | ||
|
|
3b7de31324 | ||
|
|
ebd67dd025 | ||
|
|
4557a39ae2 | ||
|
|
ce1eb299d3 | ||
|
|
20ab3f0a72 | ||
|
|
d61f90c25c | ||
|
|
90474e7592 | ||
|
|
a2e28003bc | ||
|
|
6e409c3dcd | ||
|
|
85a699c8de | ||
|
|
a788d1fb23 | ||
|
|
ab5b72375c | ||
|
|
3a023c51b5 | ||
|
|
0ad91bcafe | ||
|
|
f41ea3bcab | ||
|
|
327dc4b416 | ||
|
|
27e7779456 | ||
|
|
a7b2f5bb22 | ||
|
|
19fcb7c946 | ||
|
|
2706cd5881 | ||
|
|
4e2abd9c5b | ||
|
|
51ba2fa80a | ||
|
|
fd60cd0a21 | ||
|
|
5025372c05 | ||
|
|
4806bd075f | ||
|
|
370d17757a | ||
|
|
a648d7e75f | ||
|
|
518e1c5310 | ||
|
|
11539d8290 | ||
|
|
e4f33b58f7 | ||
|
|
073caa9515 | ||
|
|
2b06b11916 | ||
|
|
032b0f8a41 | ||
|
|
9391363f9b | ||
|
|
467fb2e9c7 | ||
|
|
257893b8de | ||
|
|
e919839de5 | ||
|
|
77d372cbee | ||
|
|
03e9facaf7 | ||
|
|
f3e459d911 | ||
|
|
36feff0e04 | ||
|
|
0320140a1b | ||
|
|
4fb1b1c9f9 | ||
|
|
37f3896a41 | ||
|
|
e1c152d95d | ||
|
|
dffac5deb8 | ||
|
|
8aa3bbfad3 | ||
|
|
5bb4006e55 | ||
|
|
e06a406682 | ||
|
|
a19719be4b | ||
|
|
35856a9f3d | ||
|
|
cf1d5d5ee2 | ||
|
|
f69a845e51 | ||
|
|
db67e93bb8 | ||
|
|
313f089222 | ||
|
|
458c08ded7 | ||
|
|
fe6cc7417f | ||
|
|
181c94a808 | ||
|
|
9ff6f02d8d | ||
|
|
3cf74516d9 | ||
|
|
31f5502c16 | ||
|
|
671d60bd72 | ||
|
|
294e866501 | ||
|
|
96789100e3 | ||
|
|
ea934fc7fb | ||
|
|
d529b7b340 | ||
|
|
092ca7f9f8 | ||
|
|
79f4bdb869 | ||
|
|
b0d2d0f7dc | ||
|
|
cdf58c1dfe | ||
|
|
4680de6022 | ||
|
|
e8f61181b6 | ||
|
|
75e9e007d0 | ||
|
|
3574ef7c0b | ||
|
|
86158036d2 | ||
|
|
e73beb47a3 | ||
|
|
0db5f1dd73 | ||
|
|
6dcdc2aa58 | ||
|
|
64601137e8 | ||
|
|
af7697b670 | ||
|
|
9e4f46088b | ||
|
|
91e71a9b9c | ||
|
|
1198ffb847 | ||
|
|
72116ecd0c | ||
|
|
04ebb7123d | ||
|
|
43096beafe | ||
|
|
2a08c62456 | ||
|
|
b62d74c3fa | ||
|
|
652fb5a6ad | ||
|
|
d1f25cc19f | ||
|
|
dc6a9d9761 | ||
|
|
838d725715 | ||
|
|
c45acb39ca | ||
|
|
957edf2a52 | ||
|
|
d969c6ed0e | ||
|
|
dd6fca961c | ||
|
|
e88a6df456 | ||
|
|
3a4530c999 | ||
|
|
b623dba40a | ||
|
|
e2fd945109 | ||
|
|
e6dc3cda31 | ||
|
|
18d8ff127a | ||
|
|
4a9526451f | ||
|
|
669d89c870 | ||
|
|
39161f2af5 | ||
|
|
be8c33e3fc | ||
|
|
7c1a27f9df | ||
|
|
342cc93dbf | ||
|
|
6f07cf439b | ||
|
|
1d4d00b69b | ||
|
|
54cb3e4d54 | ||
|
|
b0fc2caef9 | ||
|
|
1221509d24 | ||
|
|
17f5f60504 | ||
|
|
a8b0b488c6 | ||
|
|
6d922d5b70 | ||
|
|
1ab62a33e3 | ||
|
|
f9540a1e78 | ||
|
|
a720bf2c8b | ||
|
|
c143eab6fb | ||
|
|
1afb20af8b | ||
|
|
c6f27c839d | ||
|
|
742f1708c5 | ||
|
|
ac9601a48b | ||
|
|
4cd109a5fc | ||
|
|
078554a8b0 | ||
|
|
464e2f4f64 | ||
|
|
b84f55aa18 | ||
|
|
be2f780bfa | ||
|
|
262d8493a6 | ||
|
|
24e12e871b | ||
|
|
a2ee75339c | ||
|
|
9c28c4d63c | ||
|
|
7d57da1424 | ||
|
|
b77b20ba4d | ||
|
|
57ae58f093 | ||
|
|
b29c878688 | ||
|
|
5a5a6c76c1 | ||
|
|
f6a53f537c | ||
|
|
d86ed37208 | ||
|
|
92b945f28d | ||
|
|
cb164f0055 | ||
|
|
acfe37c1b5 | ||
|
|
44ded26df4 | ||
|
|
6dbd92a010 | ||
|
|
f729ca0ad2 | ||
|
|
065490c077 | ||
|
|
da6425dd93 | ||
|
|
250dcbc676 | ||
|
|
de4ca4a88f | ||
|
|
723a0c890e | ||
|
|
7085e7df20 | ||
|
|
09496d60f3 | ||
|
|
bf6bc26a5c | ||
|
|
a7217eae15 | ||
|
|
2a532ef9b7 | ||
|
|
c304ded924 | ||
|
|
4d8c47a505 | ||
|
|
d3e0ccbf34 | ||
|
|
3c82b46148 | ||
|
|
07d7f2cc17 | ||
|
|
54d4f09ab1 | ||
|
|
e8c698b5e8 | ||
|
|
b096906ffd | ||
|
|
5a9cec12fa | ||
|
|
a2ed71751f | ||
|
|
5a3a2df920 | ||
|
|
9d07e669ea | ||
|
|
aa18f1cc35 | ||
|
|
668d78ea64 | ||
|
|
6f62581d6e | ||
|
|
06686ada5c | ||
|
|
4380482606 | ||
|
|
d5112b3d32 | ||
|
|
ef67506640 | ||
|
|
c2ea4d2793 | ||
|
|
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 |
No files matched your search
@@ -0,0 +1,7 @@
|
||||
* text=auto
|
||||
|
||||
*.sh text eol=lf
|
||||
gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
|
||||
*.jar binary
|
||||
@@ -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,63 @@
|
||||
name: Bug Report
|
||||
description: Plugin does not work as described.
|
||||
labels: ["Type: 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: Anything else you want to provide that will help in resolving the issue faster, such as screenshots or videos.
|
||||
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 ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- 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,57 @@
|
||||
name: Error
|
||||
description: Error message with a stack trace in console or error file.
|
||||
labels: ["Type: 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 ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- 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,56 @@
|
||||
name: Memory leak
|
||||
description: Memory leak caused by TAB.
|
||||
labels: ["Type: Memory leak"]
|
||||
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: Heap dump
|
||||
description: Link to uploaded memory dump which I can look into.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Uncleared object(s)
|
||||
description: Objects you believe should have been removed from the memory but are not
|
||||
placeholder: |
|
||||
There is only 1 player online, but heap dump has 100 instances of TabPlayer
|
||||
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 download link of my heap dump
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: I have explained what I believe is wrong with the heap dump
|
||||
required: true
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Performance issue
|
||||
description: Performance issue in TAB or in server caused by TAB
|
||||
labels: ["Type: Performance"]
|
||||
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: Evidence
|
||||
description: Any evidence proving the performance issue, such as output of /tab cpu or timings.
|
||||
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 sufficient evidence of the issue
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Wiki change
|
||||
description: Change to text on a wiki page or adding a new section.
|
||||
labels: ["Type: Wiki change"]
|
||||
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,45 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v4
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0 #https://github.com/actions/checkout/releases
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
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@v3.1.2 #https://github.com/actions/upload-artifact/releases
|
||||
with:
|
||||
name: Jar
|
||||
path: jar/build/libs/TAB-**.jar
|
||||
+10
-6
@@ -1,8 +1,7 @@
|
||||
# Compiled class file
|
||||
*.class
|
||||
/bin/
|
||||
/target/
|
||||
/src/test/
|
||||
bin/
|
||||
target/
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
@@ -31,8 +30,13 @@ hs_err_pid*
|
||||
.project
|
||||
|
||||
# intellij
|
||||
/.idea/
|
||||
.idea/
|
||||
|
||||
# other
|
||||
.gitattributes
|
||||
*.iml
|
||||
*.iml
|
||||
run/
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -0,0 +1,9 @@
|
||||
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) 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.
|
||||
@@ -1,4 +1,4 @@
|
||||
Apache License
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
@@ -172,30 +172,3 @@ Apache License
|
||||
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,47 +1,23 @@
|
||||
## 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.
|
||||
If you have a great argument why was making this open-source worth not only for those looking for premium features for free, please take your time and explain it to: Lionel#5648 (more names coming soon).
|
||||
|
||||
|
||||
## Compiling
|
||||
Plugin can be compiled using maven. Once you've purchased the premium version, you can enable premium features by setting [this line to return true](https://github.com/NEZNAMY/TAB/blob/master/src/main/java/me/neznamy/tab/shared/TAB.java#L79) and apply your code improvements. Enabling it without purchasing the plugin may result in the features not working correctly.
|
||||
**No support is provided for self-compiled jars, only for official compilations.** If you compiled it yourself, it is highly likely you modified something, which might be the reason why is the plugin not working. If you didn't modify anything, you can just get the plugin from official download places.
|
||||
|
||||
|
||||
## Links
|
||||
Free version
|
||||
Spigot (releases): https://www.spigotmc.org/resources/57806/
|
||||
Pre-releases https://github.com/NEZNAMY/TAB/releases
|
||||
Premium version
|
||||
Polymart (releases): https://polymart.org/resource/484
|
||||
MC-Market (releases): https://www.mc-market.org/resources/14009/
|
||||
Pre-releases on discord for verified buyers
|
||||
Bukkit bridge https://www.spigotmc.org/resources/83966/
|
||||
Wiki https://github.com/NEZNAMY/TAB/wiki
|
||||
Discord invite link: https://discord.gg/EaSvdk6
|
||||
|
||||
|
||||
## 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>
|
||||
# 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 and compatibility.
|
||||
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
|
||||
|
||||
<dependencies>
|
||||
<!-- TAB -->
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>2.8.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
# Compiling
|
||||
Compilation requires JDK 17 and up.
|
||||
To compile the plugin, run `./gradlew build` from the terminal.
|
||||
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
|
||||
The universal jar contains all modules for all supported platforms.
|
||||
|
||||
# Documentation
|
||||
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
|
||||
of all features, as well as information regarding compatibility or limitations of each feature.
|
||||
|
||||
# Issues
|
||||
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is an issue tracker for all the kind of problems covered in issue templates.
|
||||
Please refrain from attempting to use it to receive help with your server, such issues will be closed immediately.
|
||||
When opening an issue, provide as much information as possible. The difference between a bug getting fixed in 1 week vs 1 hour
|
||||
is in quality of the report. Bug reports where steps to reproduce are "idk" have no value and will not help to get the bug fixed at all.
|
||||
|
||||
# Contributing
|
||||
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
|
||||
@@ -0,0 +1,2 @@
|
||||
# Security Policy
|
||||
If you found a security issue, send me a private message anywhere.
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies {
|
||||
compileOnlyApi("org.jetbrains:annotations:24.0.1")
|
||||
}
|
||||
|
||||
tasks.javadoc {
|
||||
enabled = project.hasProperty("enable-javadoc")
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.api.bossbar.BossBarManager;
|
||||
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.tablist.SortingManager;
|
||||
import me.neznamy.tab.api.tablist.HeaderFooterManager;
|
||||
import me.neznamy.tab.api.tablist.TabListFormatManager;
|
||||
import me.neznamy.tab.api.nametag.NameTagManager;
|
||||
import me.neznamy.tab.api.tablist.layout.LayoutManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The primary API class to get instances of other API classes
|
||||
*/
|
||||
public abstract class TabAPI {
|
||||
|
||||
/** Instance of the API */
|
||||
@Setter private static TabAPI instance;
|
||||
|
||||
/**
|
||||
* Returns API instance. If instance was not set by the plugin, throws
|
||||
* {@code IllegalStateException}. This is usually caused by shading the API
|
||||
* into own project, which is not allowed. Another option is calling the method
|
||||
* before plugin was able to load.
|
||||
*
|
||||
* @return API instance
|
||||
* @throws IllegalStateException
|
||||
* If instance is {@code null}
|
||||
*/
|
||||
public static @NotNull TabAPI getInstance() {
|
||||
if (instance == null) throw new IllegalStateException("The API instance is null. This can have 2 possible causes: \n" +
|
||||
"#1 - API was called before TAB was loaded. This means your plugin was loaded before TAB was. To make sure your " +
|
||||
"plugin loads after TAB, add it as a depend or soft depend of your plugin.\n" +
|
||||
"#2 - You shaded TAB's classes into your plugin, instead of only using them. This is not allowed. To verify this " +
|
||||
"is your case, unzip your plugin and check for TAB's classes. If they are there, you will need to fix your compiler " +
|
||||
"to not include them, such as scope provided for maven compilation.");
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player object from given UUID
|
||||
*
|
||||
* @param id
|
||||
* Player UUID
|
||||
* @return player object from given UUID
|
||||
*/
|
||||
public abstract @Nullable TabPlayer getPlayer(@NonNull UUID id);
|
||||
|
||||
/**
|
||||
* Returns player object from given name
|
||||
*
|
||||
* @param name
|
||||
* Player name
|
||||
* @return player object from given name
|
||||
*/
|
||||
public abstract @Nullable TabPlayer getPlayer(@NonNull 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 @NotNull TabPlayer[] getOnlinePlayers();
|
||||
|
||||
/**
|
||||
* Return BossBar manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return BossBar manager
|
||||
*/
|
||||
public abstract @Nullable BossBarManager getBossBarManager();
|
||||
|
||||
/**
|
||||
* Returns scoreboard manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return scoreboard manager
|
||||
*/
|
||||
public abstract @Nullable ScoreboardManager getScoreboardManager();
|
||||
|
||||
/**
|
||||
* Returns team manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return team manager
|
||||
*/
|
||||
public abstract @Nullable NameTagManager getNameTagManager();
|
||||
|
||||
/**
|
||||
* Returns header/footer manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return Header/footer manager
|
||||
*/
|
||||
public abstract @Nullable HeaderFooterManager getHeaderFooterManager();
|
||||
|
||||
/**
|
||||
* Returns PlaceholderManager instance
|
||||
*
|
||||
* @return PlaceholderManager instance
|
||||
*/
|
||||
public abstract @NotNull PlaceholderManager getPlaceholderManager();
|
||||
|
||||
/**
|
||||
* Returns TabList name format manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return TabList name format manager
|
||||
*/
|
||||
public abstract @Nullable TabListFormatManager getTabListFormatManager();
|
||||
|
||||
/**
|
||||
* Returns Layout manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return Layout manager
|
||||
*/
|
||||
public abstract @Nullable LayoutManager getLayoutManager();
|
||||
|
||||
/**
|
||||
* Returns Sorting manager instance if at least one feature capable of sorting players is enabled.
|
||||
* If not, returns {@code null}.
|
||||
*
|
||||
* @return Sorting manager
|
||||
*/
|
||||
public abstract @Nullable SortingManager getSortingManager();
|
||||
|
||||
/**
|
||||
* Gets the event bus for registering listeners for TAB events.
|
||||
*
|
||||
* @return the event bus
|
||||
*/
|
||||
public abstract @Nullable EventBus getEventBus();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* An interface representing a player
|
||||
*/
|
||||
public interface TabPlayer {
|
||||
|
||||
/**
|
||||
* Returns player's name
|
||||
*
|
||||
* @return Player's name
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns player's UUID
|
||||
*
|
||||
* @return Player's UUID
|
||||
*/
|
||||
@NotNull UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Returns platform-specific entity
|
||||
*
|
||||
* @return an instance of bukkit/bungee player depending on platform
|
||||
*/
|
||||
@NotNull Object getPlayer();
|
||||
|
||||
/**
|
||||
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
|
||||
*
|
||||
* @return {@code true} if player is fully loaded, {@code false} otherwise
|
||||
*/
|
||||
boolean isLoaded();
|
||||
|
||||
/**
|
||||
* Returns player's primary permission group. If group has been changed using
|
||||
* {@link #setTemporaryGroup(String)}, returns that value. Otherwise, returns group
|
||||
* detected by standard group assign logic
|
||||
*
|
||||
* @return player's primary permission group
|
||||
*/
|
||||
@NotNull String getGroup();
|
||||
|
||||
/**
|
||||
* Temporarily overrides player's group and applies all changes coming from new group.
|
||||
* This includes all properties and sorting, if used.
|
||||
* Set to {@code null} to reset back.
|
||||
*
|
||||
* @param group
|
||||
* New group to use
|
||||
* @see #hasTemporaryGroup()
|
||||
*/
|
||||
void setTemporaryGroup(@Nullable String group);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a temporary group was applied to the player using {@link #setTemporaryGroup(String)}.
|
||||
* If no group was set, returns {@code false}
|
||||
*
|
||||
* @return {@code true} if group is set, {@code false} if not
|
||||
* @see #setTemporaryGroup(String)
|
||||
*/
|
||||
boolean hasTemporaryGroup();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
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,22 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* An enum representing all available BossBar styles.
|
||||
* Calling ordinal() will return style's network ID.
|
||||
* Style names are equal to NMS names.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
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 */
|
||||
@Getter private final String bukkitName;
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@NotNull 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
|
||||
*/
|
||||
@NotNull 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(@NonNull 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(@NonNull 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(@NonNull 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(@NonNull 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(@NonNull 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(@NonNull 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
|
||||
*/
|
||||
@NotNull 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
|
||||
*/
|
||||
@NotNull 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
|
||||
*/
|
||||
@NotNull 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
|
||||
*/
|
||||
@NotNull 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(@NonNull 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(@NonNull 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
|
||||
*/
|
||||
@NotNull List<TabPlayer> getPlayers();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this bossbar is visible to specified player based on
|
||||
* display condition or adding via {@link #addPlayer(TabPlayer)}, {@code false} if not.
|
||||
* <p>
|
||||
* This may return {@code true} even if player cannot physically see the bossbar due to
|
||||
* having it disabled via toggle command or {@link BossBarManager#toggleBossBar(TabPlayer, boolean)}
|
||||
* (or {@link BossBarManager#setBossBarVisible(TabPlayer, boolean, boolean)}).
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if player is allowed to see this Boss Bar, {@code false} if not.
|
||||
*/
|
||||
boolean containsPlayer(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this boss bar is configured as announcement-only bar,
|
||||
* {@code false} if not.
|
||||
*
|
||||
* @return {@code true} if bar is announcement only, {@code false} if not
|
||||
*/
|
||||
boolean isAnnouncementBar();
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface allowing work with BossBars such as creating, sending
|
||||
* and toggling.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getBossBarManager()}.
|
||||
* This requires the BossBar feature to be enabled in config, otherwise the method will
|
||||
* return {@code 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
|
||||
*/
|
||||
@NotNull BossBar createBossBar(@NonNull String title, float progress, @NonNull BarColor color, @NonNull 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
|
||||
*/
|
||||
@NotNull BossBar createBossBar(@NonNull String title, @NonNull String progress, @NonNull String color, @NonNull 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 {@code null} if no such BossBar exists.
|
||||
*
|
||||
* @param name
|
||||
* Name of registered BossBar
|
||||
* @return BossBar with specified name
|
||||
*/
|
||||
@Nullable BossBar getBossBar(@NonNull String name);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@NotNull 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(@NonNull 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(@NonNull 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(@NonNull 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(@NonNull TabPlayer player, @NonNull 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(@NonNull 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)
|
||||
*/
|
||||
@NotNull List<BossBar> getAnnouncedBossBars();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p>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(@NonNull 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(@NonNull Class<E> type, @NonNull 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(@NonNull 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(@NonNull 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.
|
||||
*
|
||||
* <p>This is a marker interface. All events <strong>must</strong> implement this
|
||||
* interface.
|
||||
*/
|
||||
public interface TabEvent {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package me.neznamy.tab.api.event.player;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@NotNull TabPlayer getPlayer();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player was loaded because they joined,
|
||||
* {@code false} if the player was loaded because tab reload command was executed
|
||||
*
|
||||
* @return {@code true} if player joined, {@code false} if plugin was reloaded
|
||||
*/
|
||||
boolean isJoin();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package me.neznamy.tab.api.event.plugin;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Called when an unknown placeholder is about to be registered.
|
||||
* It defaults to a PlaceholderAPI implementation, but can be overridden.
|
||||
*/
|
||||
public interface PlaceholderRegisterEvent extends TabEvent {
|
||||
|
||||
/**
|
||||
* Returns placeholder identifier starting and ending with {@code %}
|
||||
*
|
||||
* @return placeholder identifier starting and ending with {@code %}
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified server placeholder implementation.
|
||||
*
|
||||
* @param supplier
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setServerPlaceholder(@NonNull Supplier<Object> supplier);
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified player placeholder implementation.
|
||||
*
|
||||
* @param function
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setPlayerPlaceholder(@NonNull Function<TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified relational placeholder implementation.
|
||||
*
|
||||
* @param function
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setRelationalPlaceholder(@NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
|
||||
}
|
||||
@@ -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,183 @@
|
||||
package me.neznamy.tab.api.nametag;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for manipulating player name tags.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getNameTagManager()}.
|
||||
* This requires the Team feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
public interface NameTagManager {
|
||||
|
||||
/**
|
||||
* Makes player's NameTag globally invisible
|
||||
*
|
||||
* @param player
|
||||
* player to hide name tag of
|
||||
* @see #showNameTag(TabPlayer)
|
||||
* @see #hasHiddenNameTag(TabPlayer)
|
||||
*/
|
||||
void hideNameTag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Hides player's NameTag for specified player until it's shown again
|
||||
*
|
||||
* @param player
|
||||
* player to hide name tag of
|
||||
* @param viewer
|
||||
* player to hide name tag for
|
||||
*/
|
||||
void hideNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Makes player's NameTag visible again
|
||||
*
|
||||
* @param player
|
||||
* player to show name tag of
|
||||
* @see #hideNameTag(TabPlayer)
|
||||
* @see #hasHiddenNameTag(TabPlayer)
|
||||
*/
|
||||
void showNameTag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Shows player's NameTag for specified viewer if it was hidden before
|
||||
*
|
||||
* @param player
|
||||
* player to show name tag of
|
||||
* @param viewer
|
||||
* player to show NameTag back for
|
||||
*/
|
||||
void showNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Return whether player has hidden NameTag or not
|
||||
*
|
||||
* @param player
|
||||
* player to check name tag visibility status of
|
||||
* @return Whether player has hidden NameTag or not
|
||||
* @see #hideNameTag(TabPlayer)
|
||||
* @see #showNameTag(TabPlayer)
|
||||
*/
|
||||
boolean hasHiddenNameTag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns true if NameTag is hidden for specified viewer, false if not
|
||||
*
|
||||
* @param player
|
||||
* player to check visibility status of
|
||||
* @param viewer
|
||||
* player to check visibility status for
|
||||
* @return true if hidden, false if not
|
||||
*/
|
||||
boolean hasHiddenNameTag(@NonNull TabPlayer player, @NonNull 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.
|
||||
*
|
||||
* @param player
|
||||
* player to pause team handling of
|
||||
*/
|
||||
void pauseTeamHandling(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
|
||||
*
|
||||
* @param player
|
||||
* player to resume team handling of
|
||||
*/
|
||||
void resumeTeamHandling(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
|
||||
* it was resumed already using resumeTeamHandling
|
||||
*
|
||||
* @param player
|
||||
* player to check handling status of
|
||||
* @return true if paused, false if not
|
||||
*/
|
||||
boolean hasTeamHandlingPaused(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Forces collision rule for the player. Setting it to null will remove forced value
|
||||
*
|
||||
* @param player
|
||||
* player to set collision rule of
|
||||
* @param collision
|
||||
* forced collision rule
|
||||
*/
|
||||
void setCollisionRule(@NonNull TabPlayer player, @Nullable Boolean collision);
|
||||
|
||||
/**
|
||||
* Returns forced collision rule or null if collision is not forced using setCollisionRule
|
||||
*
|
||||
* @param player
|
||||
* player to get forced collision of
|
||||
* @return forced value or null if not forced
|
||||
*/
|
||||
@Nullable Boolean getCollisionRule(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes player's prefix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param prefix
|
||||
* new prefix value
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
|
||||
|
||||
/**
|
||||
* Changes player's suffix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param suffix
|
||||
* new suffix value
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
|
||||
|
||||
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
|
||||
|
||||
@NonNull String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Toggles name tag visibility view on all players for specified player.
|
||||
* On first call, name tags of all players will become invisible for specified
|
||||
* player. On second call, they will become visible again.
|
||||
*
|
||||
* @param player
|
||||
* player to toggle name tag visibility view for
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if configured toggle message should be sent, {@code false} if not
|
||||
*/
|
||||
void toggleNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has hidden name tags 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(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package me.neznamy.tab.api.nametag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for manipulating player name tags.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getNameTagManager()} and then casting it.
|
||||
* This requires both Team and Unlimited name tag features to be enabled in config, otherwise the method will
|
||||
* return {@code null} or not be an instance of this class.
|
||||
*/
|
||||
public interface UnlimitedNameTagManager extends NameTagManager {
|
||||
|
||||
/**
|
||||
* Disables armor stands of specified player and displays vanilla name tags instead.
|
||||
*
|
||||
* @param player
|
||||
* Player to disable armor stands of
|
||||
* @see #enableArmorStands(TabPlayer)
|
||||
* @see #hasDisabledArmorStands(TabPlayer)
|
||||
*/
|
||||
void disableArmorStands(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Enabled back armor stands of player, who was disabled previously using
|
||||
* {@link #disableArmorStands(TabPlayer)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to enable back
|
||||
* @see #disableArmorStands(TabPlayer)
|
||||
* @see #hasDisabledArmorStands(TabPlayer)
|
||||
*/
|
||||
void enableArmorStands(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has armor stands disabled using
|
||||
* {@link #disableArmorStands(TabPlayer)}, {@code false} otherwise.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if disabled, {@code false} if not.
|
||||
* @see #disableArmorStands(TabPlayer)
|
||||
* @see #enableArmorStands(TabPlayer)
|
||||
*/
|
||||
boolean hasDisabledArmorStands(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes name to specified value. Set to {@code null} to
|
||||
* reset back to original value.
|
||||
*
|
||||
* @param player
|
||||
* Player to change name of
|
||||
* @param customName
|
||||
* New name, or {@code null} to reset to original value
|
||||
*/
|
||||
void setName(@NonNull TabPlayer player, @Nullable String customName);
|
||||
|
||||
/**
|
||||
* Changes line to specified value. Set to {@code null} to reset back to original value.
|
||||
*
|
||||
* @param player
|
||||
* Player to change line of
|
||||
* @param line
|
||||
* Name of defined line
|
||||
* @param value
|
||||
* Value to use, or {@code null} to reset back to original value
|
||||
* @see #getDefinedLines()
|
||||
*/
|
||||
void setLine(@NonNull TabPlayer player, @NonNull String line, @Nullable String value);
|
||||
|
||||
/**
|
||||
* Returns custom name value set using {@link #setName(TabPlayer, String)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return Custom name set previously or {@code null} if not set
|
||||
* @see #setName(TabPlayer, String)
|
||||
*/
|
||||
@Nullable String getCustomName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns custom line value set using {@link #setLine(TabPlayer, String, String)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @param line
|
||||
* Defined line
|
||||
* @return Custom name set previously or {@code null} if not set
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getDefinedLines()
|
||||
*/
|
||||
@Nullable String getCustomLineValue(@NonNull TabPlayer player, @NonNull String line);
|
||||
|
||||
/**
|
||||
* Returns original name value configured in files.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return Original configured name
|
||||
* @see #setName(TabPlayer, String)
|
||||
*/
|
||||
@NotNull String getOriginalName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original line value configured in files.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @param line
|
||||
* Line name
|
||||
* @return Original configured line
|
||||
* @see #setLine(TabPlayer, String, String)
|
||||
*/
|
||||
@NotNull String getOriginalLineValue(@NonNull TabPlayer player, @NonNull String line);
|
||||
|
||||
/**
|
||||
* Returns list of all defined lines in config, both static and dynamic lines.
|
||||
*
|
||||
* @return List of all defined lines in config
|
||||
*/
|
||||
@NotNull List<String> getDefinedLines();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An interface representing some kind of placeholder
|
||||
*/
|
||||
public interface Placeholder {
|
||||
|
||||
/**
|
||||
* Returns refresh interval of this placeholder
|
||||
*
|
||||
* @return refresh interval of this placeholder
|
||||
*/
|
||||
int getRefresh();
|
||||
|
||||
/**
|
||||
* Returns placeholder's identifier
|
||||
*
|
||||
* @return placeholder's identifier
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* An interface allowing placeholder registration. Instance can be
|
||||
* obtained using {@link TabAPI#getPlaceholderManager()}
|
||||
*/
|
||||
public interface PlaceholderManager {
|
||||
|
||||
/**
|
||||
* Registers a server placeholder (placeholder with same output for all players)
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Refresh interval
|
||||
* @param supplier
|
||||
* Supplier for placeholder output
|
||||
* @return Registered placeholder for further use
|
||||
* @throws IllegalArgumentException
|
||||
* If {@code identifier} does not start and end with {@code %} or
|
||||
* {@code refresh} is not divisible by 50
|
||||
*/
|
||||
@NotNull ServerPlaceholder registerServerPlaceholder(@NonNull String identifier, int refresh, @NonNull Supplier<Object> supplier);
|
||||
|
||||
/**
|
||||
* Registers a player placeholder (placeholder different output per player)
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Refresh interval
|
||||
* @param function
|
||||
* Function for placeholder output
|
||||
* @return Registered placeholder for further use
|
||||
* @throws IllegalArgumentException
|
||||
* If {@code identifier} does not start and end with {@code %} or
|
||||
* {@code refresh} is not divisible by 50
|
||||
*/
|
||||
@NotNull PlayerPlaceholder registerPlayerPlaceholder(@NonNull String identifier, int refresh, @NonNull Function<TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Registers a relational placeholder (placeholder with output different for each player duo)
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Refresh interval
|
||||
* @param function
|
||||
* Function for placeholder output
|
||||
* @return Registered placeholder for further use
|
||||
* @throws IllegalArgumentException
|
||||
* If {@code identifier} does not start and end with {@code %},
|
||||
* does not start with {@code %rel_} or
|
||||
* {@code refresh} is not divisible by 50
|
||||
*/
|
||||
@NotNull RelationalPlaceholder registerRelationalPlaceholder(@NonNull String identifier, int refresh, @NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Returns placeholder from specified identifier. If it does not exist, it is registered
|
||||
* as a PlaceholderAPI placeholder and returned.
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @return Placeholder with specified identifier
|
||||
*/
|
||||
@NotNull Placeholder getPlaceholder(@NonNull String identifier);
|
||||
|
||||
/**
|
||||
* Unregisters placeholder and makes plugin no longer refresh it.
|
||||
*
|
||||
* @param placeholder
|
||||
* Placeholder to unregister
|
||||
*/
|
||||
void unregisterPlaceholder(@NonNull Placeholder placeholder);
|
||||
|
||||
/**
|
||||
* Unregisters placeholder and makes plugin no longer refresh it.
|
||||
*
|
||||
* @param identifier
|
||||
* identifier of placeholder to unregister
|
||||
*/
|
||||
void unregisterPlaceholder(@NonNull String identifier);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for player placeholders (placeholders returning different value for each player)
|
||||
*/
|
||||
public interface PlayerPlaceholder extends Placeholder {
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
|
||||
* so this is (only) useful for force an update when value is supposed to change, and you have
|
||||
* a high refresh interval set.
|
||||
*
|
||||
* @param player
|
||||
* Player to update value for
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull TabPlayer player, @NonNull Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*
|
||||
* @param player
|
||||
* Player to update value for
|
||||
*/
|
||||
void update(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* An interface for relational placeholders (returning different value for each player duo)
|
||||
*/
|
||||
public interface RelationalPlaceholder extends Placeholder {
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
|
||||
* so this is (only) useful for force an update when value is supposed to change, and you have
|
||||
* a high refresh interval set.
|
||||
*
|
||||
* @param viewer
|
||||
* Placeholder viewer
|
||||
* @param target
|
||||
* Placeholder target
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @NonNull Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*
|
||||
* @param viewer
|
||||
* Placeholder viewer
|
||||
* @param target
|
||||
* Placeholder target
|
||||
*/
|
||||
void update(@NonNull TabPlayer viewer, @NonNull TabPlayer target);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* An interface for server placeholders (returning same value for all players)
|
||||
*/
|
||||
public interface ServerPlaceholder extends Placeholder {
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
|
||||
* so this is (only) useful for force an update when value is supposed to change, and you have
|
||||
* a high refresh interval set.
|
||||
*
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*/
|
||||
void update();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@NotNull 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(@NonNull String text);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns raw title of this scoreboard. Placeholders stay in their
|
||||
* raw format.
|
||||
*
|
||||
* @return scoreboard title
|
||||
* @see #setTitle(String)
|
||||
*/
|
||||
@NotNull 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(@NonNull 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)
|
||||
*/
|
||||
@NotNull 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(@NonNull 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,169 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* 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 {@code 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}
|
||||
*/
|
||||
@NotNull Scoreboard createScoreboard(@NonNull String name, @NonNull String title, @NonNull 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
|
||||
*/
|
||||
@NotNull 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(@NonNull TabPlayer player, @NonNull 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(@NonNull 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(@NonNull 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(@NonNull 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(@NonNull 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(@NonNull 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(@NonNull 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
|
||||
*/
|
||||
@Nullable Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface allowing to send header/footer to players.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getHeaderFooterManager()}.
|
||||
* This requires the Header/footer feature to be enabled in config, otherwise
|
||||
* the method will return {@code null}.
|
||||
*/
|
||||
public interface HeaderFooterManager {
|
||||
|
||||
/**
|
||||
* Sets raw value of header to specified value. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value. Use {@code null} to reset value back to original.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param header
|
||||
* Raw header value
|
||||
*/
|
||||
void setHeader(@NonNull TabPlayer player, @Nullable String header);
|
||||
|
||||
/**
|
||||
* Sets raw value of footer to specified value. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value. Use {@code null} to reset value back to original.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param footer
|
||||
* Raw footer value
|
||||
*/
|
||||
void setFooter(@NonNull TabPlayer player, @Nullable String footer);
|
||||
|
||||
/**
|
||||
* Sets raw value of header and footer to specified values. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value. Use {@code null} to reset value back to original.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param header
|
||||
* Raw header value
|
||||
* @param footer
|
||||
* Raw footer value
|
||||
*/
|
||||
void setHeaderAndFooter(@NonNull TabPlayer player, @Nullable String header, @Nullable String footer);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface SortingManager {
|
||||
|
||||
/**
|
||||
* 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 player
|
||||
* player to set team name of
|
||||
* @param teamName
|
||||
* forced team name
|
||||
*/
|
||||
void forceTeamName(@NonNull TabPlayer player, @Nullable String teamName);
|
||||
|
||||
/**
|
||||
* Returns forced team name of player or null if not forced
|
||||
*
|
||||
* @param player
|
||||
* player to check forced team name of
|
||||
* @return forced team name of player or null if not forced
|
||||
*/
|
||||
@Nullable String getForcedTeamName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original team name of the player set by the plugin.
|
||||
*
|
||||
* @param player
|
||||
* Player to get team name of
|
||||
* @return Player's team name based on configuration.
|
||||
*/
|
||||
@NotNull String getOriginalTeamName(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface allowing to modify TabList display names
|
||||
* of players.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getTabListFormatManager()}.
|
||||
* This requires the feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
public interface TabListFormatManager {
|
||||
|
||||
/**
|
||||
* Changes player's prefix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param prefix
|
||||
* new prefix value
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
|
||||
|
||||
/**
|
||||
* Changes player's name to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param customName
|
||||
* new customName value
|
||||
* @see #getCustomName(TabPlayer)
|
||||
* @see #getOriginalName(TabPlayer)
|
||||
*/
|
||||
void setName(@NonNull TabPlayer player, @Nullable String customName);
|
||||
|
||||
/**
|
||||
* Changes player's suffix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param suffix
|
||||
* new suffix value
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
/**
|
||||
* Returns prefix assigned using {@link #setPrefix(TabPlayer, String)} method.
|
||||
* If the method was not used, {@code null} is returned.
|
||||
*
|
||||
* @param player
|
||||
* player to get custom prefix of
|
||||
* @return player's custom prefix or {@code null} if not defined
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns custom name assigned using {@link #setName(TabPlayer, String)} method.
|
||||
* If the method was not used, {@code null} is returned.
|
||||
*
|
||||
* @param player
|
||||
* player to get custom name of
|
||||
* @return player's custom name or {@code null} if not defined
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getOriginalName(TabPlayer)
|
||||
*/
|
||||
@Nullable String getCustomName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns suffix assigned using {@link #setSuffix(TabPlayer, String)} method.
|
||||
* If the method was not used, {@code null} is returned.
|
||||
*
|
||||
* @param player
|
||||
* player to get custom suffix of
|
||||
* @return player's custom suffix or {@code null} if not defined
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original prefix applied using plugin's internal logic.
|
||||
* This value is not affected by API calls in any way.
|
||||
*
|
||||
* @param player
|
||||
* player to get original prefix of
|
||||
* @return player's original prefix
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
*/
|
||||
@NotNull String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original name applied using plugin's internal logic.
|
||||
* This value is not affected by API calls in any way.
|
||||
*
|
||||
* @param player
|
||||
* player to get original name of
|
||||
* @return player's original name
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getCustomName(TabPlayer)
|
||||
*/
|
||||
@NotNull String getOriginalName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original suffix applied using plugin's internal logic.
|
||||
* This value is not affected by API calls in any way.
|
||||
*
|
||||
* @param player
|
||||
* player to get original suffix of
|
||||
* @return player's original suffix
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
*/
|
||||
@NotNull String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package me.neznamy.tab.api.tablist.layout;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface representing a layout.
|
||||
*/
|
||||
public interface Layout {
|
||||
|
||||
/**
|
||||
* Returns name of the layout.
|
||||
*
|
||||
* @return name of the layout
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text);
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
* @param skin
|
||||
* Skin definition like in config
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin);
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
* @param ping
|
||||
* Slot's ping
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, int ping);
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
* @param skin
|
||||
* Skin definition like in config
|
||||
* @param ping
|
||||
* Slot's ping
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin, int ping);
|
||||
|
||||
/**
|
||||
* Adds a player group.
|
||||
*
|
||||
* @param condition
|
||||
* Condition that must be met for player to appear in the group.
|
||||
* May be null for no requirement. Syntax is like in config -
|
||||
* either condition name or short format.
|
||||
* @param slots
|
||||
* Slots for this player group
|
||||
*/
|
||||
void addGroup(@Nullable String condition, int[] slots);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package me.neznamy.tab.api.tablist.layout;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for working with layouts. Instance can be obtained using
|
||||
* {@link TabAPI#getLayoutManager()}. If the feature is disabled in
|
||||
* config, it will return {@code null}.
|
||||
*/
|
||||
public interface LayoutManager {
|
||||
|
||||
/**
|
||||
* Creates new layout
|
||||
*
|
||||
* @param name
|
||||
* Unique layout name
|
||||
* @return Created layout
|
||||
*/
|
||||
Layout createNewLayout(String name);
|
||||
|
||||
/**
|
||||
* Sends layout to player. Set to {@code null} to make player not see
|
||||
* any layout, but default TabList instead.
|
||||
*
|
||||
* @param player
|
||||
* Player to send layout to
|
||||
* @param layout
|
||||
* Layout to send
|
||||
* @see #resetLayout(TabPlayer)
|
||||
*/
|
||||
void sendLayout(@NonNull TabPlayer player, @Nullable Layout layout);
|
||||
|
||||
/**
|
||||
* Reset layout back to original one based on configuration.
|
||||
*
|
||||
* @param player
|
||||
* Player to reset layout for
|
||||
* @see #sendLayout(TabPlayer, Layout)
|
||||
*/
|
||||
void resetLayout(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.johnrengelman:shadow:8.1.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.0.1")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.1-SNAPSHOT")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
rootProject.name = "build-logic"
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
maven("https://maven.fabricmc.net/")
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
id("io.freefair.lombok")
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
filter<ReplaceTokens>("tokens" to mapOf(
|
||||
"id" to rootProject.ext.get("id")!!.toString(),
|
||||
"name" to rootProject.name,
|
||||
"version" to project.version,
|
||||
"description" to project.description,
|
||||
"website" to rootProject.ext.get("website")!!.toString(),
|
||||
"author" to rootProject.ext.get("author")!!.toString()
|
||||
))
|
||||
}
|
||||
javadoc {
|
||||
// This saves a decent bit of processing power on slow machines
|
||||
enabled = false
|
||||
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,15 @@
|
||||
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")
|
||||
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.me.neznamy.yamlassist")
|
||||
relocate("org.yaml.snakeyaml", "me.neznamy.tab.libs.org.yaml.snakeyaml")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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,35 @@
|
||||
plugins {
|
||||
id("tab.parent")
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "4.0.4"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
ext.set("website", "https://github.com/NEZNAMY/TAB")
|
||||
ext.set("author", "NEZNAMY")
|
||||
}
|
||||
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bungeecord,
|
||||
projects.velocity,
|
||||
projects.sponge7,
|
||||
projects.sponge8,
|
||||
projects.fabric
|
||||
).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,13 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bukkit:3.0.1")
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
|
||||
compileOnly("me.clip:placeholderapi:2.11.2")
|
||||
compileOnly("com.mojang:authlib:1.5.25")
|
||||
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
|
||||
exclude("org.bukkit", "bukkit")
|
||||
}
|
||||
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
|
||||
exclude("org.spigotmc", "spigot")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
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 org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The core for bukkit forwarding events into all enabled features
|
||||
*/
|
||||
public class BukkitEventListener extends EventListener<Player> implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e) {
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
join(e.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
worldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onCommand(PlayerCommandPreprocessEvent e) {
|
||||
if (command(e.getPlayer().getUniqueId(), e.getMessage())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabPlayer createPlayer(@NotNull Player player) {
|
||||
return new BukkitTabPlayer((BukkitPlatform) TAB.getInstance().getPlatform(), player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Pipeline injection for bukkit
|
||||
*/
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BukkitPipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
try {
|
||||
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
|
||||
} catch (IllegalAccessException exception) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
int position;
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
position = ((Enum<?>)PacketScoreboard.DisplayObjective_POSITION.get(packet)).ordinal();
|
||||
} else {
|
||||
position = PacketScoreboard.DisplayObjective_POSITION.getInt(packet);
|
||||
}
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, position,
|
||||
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
PacketScoreboard.Objective_METHOD.getInt(packet),
|
||||
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayObjective(@NotNull Object packet) {
|
||||
return PacketScoreboard.DisplayObjectiveClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObjective(@NotNull Object packet) {
|
||||
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeam(@NotNull Object packet) {
|
||||
return PacketScoreboard.TeamPacketClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(@NotNull Object packet) {
|
||||
return BukkitTabList.PlayerInfoClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
List<String> actions;
|
||||
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
|
||||
//1.19.3+
|
||||
actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
|
||||
} else {
|
||||
//1.19.2-
|
||||
actions = Collections.singletonList(BukkitTabList.ACTION.get(packet).toString());
|
||||
}
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
|
||||
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id;
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
|
||||
} else {
|
||||
id = profile.getId();
|
||||
}
|
||||
Object displayName = null;
|
||||
int latency = 0;
|
||||
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
|
||||
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
|
||||
if (newDisplayName != null) displayName = TAB.getInstance().getPlatform().toComponent(newDisplayName, receiver.getVersion());
|
||||
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
|
||||
}
|
||||
if (actions.contains(TabList.Action.UPDATE_LATENCY.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
|
||||
latency = BukkitTabList.PlayerInfoData_Latency.getInt(nmsData);
|
||||
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, latency);
|
||||
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
|
||||
}
|
||||
if (actions.contains(TabList.Action.ADD_PLAYER.name())) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
|
||||
}
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
|
||||
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
|
||||
id,
|
||||
profile,
|
||||
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
|
||||
latency,
|
||||
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
|
||||
displayName,
|
||||
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
|
||||
}
|
||||
}
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
BukkitTabList.PLAYERS.set(packet, updatedList);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
|
||||
if (TAB.getInstance().getNameTagManager() == null) return;
|
||||
int action = PacketScoreboard.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
|
||||
if (action == 1 || action == 2 || action == 4) return;
|
||||
Collection<String> players = (Collection<String>) PacketScoreboard.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
|
||||
String teamName = (String) PacketScoreboard.TeamPacket_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;
|
||||
}
|
||||
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
String expectedTeam = sorting.getShortTeamName(p);
|
||||
if (expectedTeam == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
|
||||
logTeamOverride(teamName, p.getName(), expectedTeam);
|
||||
} else {
|
||||
newList.add(entry);
|
||||
}
|
||||
}
|
||||
PacketScoreboard.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* Main class for Bukkit.
|
||||
*/
|
||||
public class BukkitTAB extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!isVersionSupported()) {
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
TAB.create(ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer") ?
|
||||
new FoliaPlatform(this) : new BukkitPlatform(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
//null check due to compatibility check making instance not get set on unsupported versions
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all used NMS classes, constructors, fields and methods.
|
||||
* Returns {@code true} if everything went successfully,
|
||||
* {@code false} if anything went wrong.
|
||||
*
|
||||
* @return {@code true} if server version is compatible, {@code false} if not
|
||||
*/
|
||||
private boolean isVersionSupported() {
|
||||
try {
|
||||
NMSStorage.setInstance(new NMSStorage());
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is not compatible. " +
|
||||
"This plugin version was made for " + ProtocolVersion.values()[ProtocolVersion.values().length-1].getFriendlyName() +
|
||||
" - " + ProtocolVersion.values()[3].getFriendlyName() + ". Disabling."));
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] A compatibility issue " +
|
||||
"with your server was found. Unless you are running some really weird server software, this is a bug."));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Command handler for /tab command
|
||||
*/
|
||||
public class BukkitTabCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
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
|
||||
@NotNull
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof Player) {
|
||||
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
|
||||
if (p == null) return Collections.emptyList(); //player not loaded correctly
|
||||
}
|
||||
return TAB.getInstance().getCommand().complete(p, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabList.Entry.Builder;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList which support modifying many entries at once
|
||||
* for significantly better performance. For 1.7 players,
|
||||
* ViaVersion properly splits the packet into multiple, so
|
||||
* we don't need to worry about that here.
|
||||
* <p>
|
||||
* This class does not support server versions of 1.7 and
|
||||
* below, because of the massive differences in tab list
|
||||
* and packet fields.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class BukkitTabList implements TabList {
|
||||
|
||||
// NMS Fields
|
||||
private static Class<?> HeaderFooterClass;
|
||||
private static Constructor<?> newHeaderFooter;
|
||||
private static Field HEADER;
|
||||
private static Field FOOTER;
|
||||
|
||||
public static Class<?> PlayerInfoClass;
|
||||
private static Constructor<?> newPlayerInfo;
|
||||
public static Field ACTION;
|
||||
public static Field PLAYERS;
|
||||
private static Class<Enum> ActionClass;
|
||||
public static Class<?> ClientboundPlayerInfoRemovePacket;
|
||||
private static Class<?> RemoteChatSession$Data;
|
||||
private static Constructor<?> newClientboundPlayerInfoRemovePacket;
|
||||
private static Class<?> EntityPlayer;
|
||||
|
||||
public static Constructor<?> newPlayerInfoData;
|
||||
public static Field PlayerInfoData_UUID;
|
||||
public static Field PlayerInfoData_Profile;
|
||||
public static Field PlayerInfoData_Latency;
|
||||
public static Field PlayerInfoData_GameMode;
|
||||
public static Field PlayerInfoData_DisplayName;
|
||||
public static Field PlayerInfoData_Listed;
|
||||
public static Field PlayerInfoData_RemoteChatSession;
|
||||
|
||||
private static Object[] gameModes;
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
public static void load() throws NoSuchMethodException, ClassNotFoundException {
|
||||
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported (yet?)
|
||||
|
||||
// Classes
|
||||
Class<?> playerInfoDataClass;
|
||||
Class<?> IChatBaseComponent;
|
||||
Class<Enum> EnumGamemodeClass;
|
||||
if (BukkitReflection.isMojangMapped()) {
|
||||
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
|
||||
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
|
||||
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
|
||||
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
|
||||
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
|
||||
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
|
||||
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
|
||||
} else {
|
||||
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
|
||||
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
|
||||
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
|
||||
}
|
||||
} else if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
|
||||
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
|
||||
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
|
||||
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
|
||||
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
|
||||
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
|
||||
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
|
||||
} else {
|
||||
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
|
||||
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
|
||||
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
|
||||
}
|
||||
} else {
|
||||
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
|
||||
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
|
||||
HeaderFooterClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
|
||||
PlayerInfoClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo");
|
||||
ActionClass = (Class<Enum>) BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
|
||||
playerInfoDataClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
|
||||
EnumGamemodeClass = (Class<Enum>) BukkitReflection.getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
|
||||
}
|
||||
|
||||
// Header & Footer
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
newHeaderFooter = HeaderFooterClass.getConstructor(IChatBaseComponent, IChatBaseComponent);
|
||||
} else {
|
||||
newHeaderFooter = HeaderFooterClass.getConstructor();
|
||||
HEADER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(0);
|
||||
FOOTER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(1);
|
||||
}
|
||||
|
||||
// Info packet
|
||||
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
|
||||
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(playerInfoDataClass);
|
||||
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(playerInfoDataClass, GameProfile.class);
|
||||
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(playerInfoDataClass, int.class);
|
||||
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
|
||||
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(playerInfoDataClass, IChatBaseComponent);
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
|
||||
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
|
||||
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
|
||||
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
|
||||
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
|
||||
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
|
||||
} else {
|
||||
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, Array.newInstance(EntityPlayer, 0).getClass());
|
||||
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
|
||||
}
|
||||
gameModes = new Object[] {
|
||||
Enum.valueOf(EnumGamemodeClass, "SURVIVAL"),
|
||||
Enum.valueOf(EnumGamemodeClass, "CREATIVE"),
|
||||
Enum.valueOf(EnumGamemodeClass, "ADVENTURE"),
|
||||
Enum.valueOf(EnumGamemodeClass, "SPECTATOR")
|
||||
};
|
||||
available = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
if (!available) return;
|
||||
if (ClientboundPlayerInfoRemovePacket != null) {
|
||||
//1.19.3+
|
||||
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
|
||||
} else {
|
||||
//1.19.2-
|
||||
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Builder(entry).build()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
if (!available) return;
|
||||
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME, new Builder(entry).displayName(displayName).build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
if (!available) return;
|
||||
player.sendPacket(createPacket(Action.UPDATE_LATENCY, new Builder(entry).latency(latency).build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
if (!available) return;
|
||||
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE, new Builder(entry).gameMode(gameMode).build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
if (!available) return;
|
||||
player.sendPacket(createPacket(Action.ADD_PLAYER, entry));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
if (HeaderFooterClass == null) return;
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
player.sendPacket(newHeaderFooter.newInstance(toComponent(header), toComponent(footer)));
|
||||
} else {
|
||||
Object packet = newHeaderFooter.newInstance();
|
||||
HEADER.set(packet, toComponent(header));
|
||||
FOOTER.set(packet, toComponent(footer));
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private Object createPacket(@NotNull Action action, @NotNull Entry entry) {
|
||||
Object packet;
|
||||
List<Object> players = new ArrayList<>();
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
EnumSet<?> actions;
|
||||
if (action == Action.ADD_PLAYER) {
|
||||
actions = EnumSet.allOf(ActionClass);
|
||||
} else {
|
||||
actions = EnumSet.of(Enum.valueOf(ActionClass, action.name()));
|
||||
}
|
||||
packet = newPlayerInfo.newInstance(actions, Collections.emptyList());
|
||||
players.add(newPlayerInfoData.newInstance(
|
||||
entry.getUniqueId(),
|
||||
createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()),
|
||||
true,
|
||||
entry.getLatency(),
|
||||
gameModes[entry.getGameMode()],
|
||||
entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()),
|
||||
null
|
||||
));
|
||||
} else {
|
||||
packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Array.newInstance(EntityPlayer, 0));
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
|
||||
parameters.add(packet);
|
||||
}
|
||||
parameters.add(createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()));
|
||||
parameters.add(entry.getLatency());
|
||||
parameters.add(gameModes[entry.getGameMode()]);
|
||||
parameters.add(entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()));
|
||||
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
|
||||
players.add(newPlayerInfoData.newInstance(parameters.toArray()));
|
||||
}
|
||||
PLAYERS.set(packet, players);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private Object toComponent(IChatBaseComponent component) {
|
||||
return player.getPlatform().toComponent(component, player.getVersion());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NotNull UUID id, @Nullable String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name == null ? "" : name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketEntityView;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.backend.entityview.EntityView;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.EntityBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for Bukkit platform
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Getter
|
||||
public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
/** Player's NMS handle (EntityPlayer), preloading for speed */
|
||||
@NotNull
|
||||
private final Object handle;
|
||||
|
||||
/** Player's connection for sending packets, preloading for speed */
|
||||
@NotNull
|
||||
private final Object playerConnection;
|
||||
|
||||
@NotNull
|
||||
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
|
||||
|
||||
@NotNull
|
||||
private final TabList tabList = new BukkitTabList(this);
|
||||
|
||||
@NotNull
|
||||
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
|
||||
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
|
||||
|
||||
@NotNull
|
||||
private final EntityView entityView = new PacketEntityView(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given bukkit player
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param p
|
||||
* bukkit player
|
||||
*/
|
||||
@SneakyThrows
|
||||
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
|
||||
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName());
|
||||
handle = NMSStorage.getInstance().getHandle.invoke(player);
|
||||
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String permission) {
|
||||
return getPlayer().hasPermission(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getPing() {
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
return getPlayer().getPing();
|
||||
}
|
||||
return NMSStorage.getInstance().PING.getInt(handle);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void sendPacket(@NotNull Object nmsPacket) {
|
||||
if (!getPlayer().isOnline()) return;
|
||||
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return getPlayer().hasPotionEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
return LibsDisguisesHook.isDisguised(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@Nullable
|
||||
public TabList.Skin getSkin() {
|
||||
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
return new TabList.Skin(
|
||||
(String) property.getClass().getMethod("value").invoke(property),
|
||||
(String) property.getClass().getMethod("signature").invoke(property)
|
||||
);
|
||||
} else {
|
||||
return new TabList.Skin(property.getValue(), property.getSignature());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
return (Player) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer().isOnline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitPlatform getPlatform() {
|
||||
return (BukkitPlatform) platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().getGameMode().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealth() {
|
||||
return getPlayer().getHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return getPlayer().getDisplayName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Class containing static util methods used on Bukkit.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class BukkitUtils {
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API. This requires reflection, as return type changed in 1.8,
|
||||
* and we want to avoid errors.
|
||||
*
|
||||
* @return Online players from Bukkit API.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public static Player[] getOnlinePlayers() {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
|
||||
* If not, closest legacy color is used instead.
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param rgbClient
|
||||
* Whether client accepts RGB colors or not.
|
||||
* @return Converted string using bukkit color format
|
||||
*/
|
||||
@NotNull
|
||||
public static String toBukkitFormat(@NotNull IChatBaseComponent component, boolean rgbClient) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (component.getModifier().getColor() != null) {
|
||||
if (BukkitReflection.getMinorVersion() >= 16 && rgbClient) {
|
||||
String hexCode = component.getModifier().getColor().getHexCode();
|
||||
char c = EnumChatFormat.COLOR_CHAR;
|
||||
sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1))
|
||||
.append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3))
|
||||
.append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5));
|
||||
} else {
|
||||
sb.append(component.getModifier().getColor().getLegacyColor().getFormat());
|
||||
}
|
||||
}
|
||||
if (component.getModifier().isBold()) sb.append(EnumChatFormat.BOLD.getFormat());
|
||||
if (component.getModifier().isStrikethrough()) sb.append(EnumChatFormat.STRIKETHROUGH.getFormat());
|
||||
if (component.getModifier().isItalic()) sb.append(EnumChatFormat.ITALIC.getFormat());
|
||||
if (component.getModifier().isObfuscated()) sb.append(EnumChatFormat.OBFUSCATED.getFormat());
|
||||
if (component.getModifier().isUnderlined()) sb.append(EnumChatFormat.UNDERLINE.getFormat());
|
||||
if (component.getText() != null) sb.append(component.getText());
|
||||
for (IChatBaseComponent extra : component.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra, rgbClient));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* BossBar for 1.9+ servers where Bukkit API is used. If ViaVersion is used
|
||||
* to allow 1.8 players, it will handle the entity and teleporting by itself.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitBossBar implements BossBar {
|
||||
|
||||
/** Player this handler belongs to */
|
||||
@NotNull
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
/** Bukkit BossBars the player can currently see */
|
||||
@NotNull
|
||||
private final Map<UUID, org.bukkit.boss.BossBar> bossBars = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
if (bossBars.containsKey(id)) return;
|
||||
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
|
||||
BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16),
|
||||
org.bukkit.boss.BarColor.valueOf(color.name()),
|
||||
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
|
||||
bar.setProgress(progress);
|
||||
bar.addPlayer(player.getPlayer());
|
||||
bossBars.put(id, bar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
bossBars.get(id).setTitle(BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
bossBars.get(id).setProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
bossBars.remove(id).removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* BossBar using wither entity for <1.9 players on <1.9 servers.
|
||||
* Additional logic, such as teleporting the entity must be done
|
||||
* separately, as this class does not handle it.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class EntityBossBar implements BossBar {
|
||||
|
||||
/** Player this handler belongs to */
|
||||
@NotNull
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
float health = 300*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.setHealth(health);
|
||||
w.setCustomName(title, player.getVersion());
|
||||
w.setEntityFlags((byte) 32);
|
||||
w.setWitherInvulnerableTime(880); // Magic number
|
||||
player.getEntityView().spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
w.setCustomName(title, player.getVersion());
|
||||
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
float health = 300*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.setHealth(health);
|
||||
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {/*Added in 1.9*/}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {/*Added in 1.9*/}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
player.getEntityView().destroyEntities(id.hashCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Handler for 1.9+ players on 1.8 server using ViaVersion API.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ViaBossBar implements BossBar {
|
||||
|
||||
/** Player this handler belongs to */
|
||||
@NotNull
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
|
||||
@NotNull
|
||||
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
if (viaBossBars.containsKey(id)) return;
|
||||
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
|
||||
IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()),
|
||||
progress,
|
||||
BossColor.valueOf(color.name()),
|
||||
BossStyle.valueOf(style.getBukkitName()));
|
||||
viaBossBars.put(id, bar);
|
||||
bar.addPlayer(player.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
viaBossBars.get(id).setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
viaBossBars.get(id).setHealth(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
viaBossBars.get(id).setStyle(BossStyle.valueOf(style.getBukkitName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
viaBossBars.remove(id).removePlayer(player.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.Bukkit;
|
||||
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 org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The core class for unlimited NameTag mode on Bukkit
|
||||
*/
|
||||
public class BukkitNameTagX extends BackendNameTagX implements Listener {
|
||||
|
||||
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onSneak(PlayerToggleSneakEvent e) {
|
||||
sneak(e.getPlayer().getUniqueId(), e.isSneaking());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
respawn(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDistance(@NotNull TabPlayer player1, @NotNull 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 boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
|
||||
return ((Player) player1.getPlayer()).getWorld() == ((Player) player2.getPlayer()).getWorld();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
return ((Player)viewer.getPlayer()).canSee((Player) target.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListener() {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
@NotNull
|
||||
public List<Integer> getPassengers(@NotNull Object entity) {
|
||||
Entity vehicle = (Entity) entity;
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
|
||||
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
|
||||
} else {
|
||||
if (vehicle.getPassenger() != null) {
|
||||
return Collections.singletonList(vehicle.getPassenger().getEntityId());
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getVehicle(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getVehicle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEntityId(@NotNull Object entity) {
|
||||
return ((Entity)entity).getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getEntityType(@NotNull Object entity) {
|
||||
return ((Entity) entity).getType().toString().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).isSneaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSwimming(@NotNull TabPlayer player) {
|
||||
Player p = (Player) player.getPlayer();
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
|
||||
return TAB.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGliding(@NotNull TabPlayer player) {
|
||||
return TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSleeping(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).isSleeping();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object getArmorStandType() {
|
||||
return EntityType.ARMOR_STAND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getX(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getLocation().getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getY(@NotNull Object entity) {
|
||||
return ((Entity)entity).getLocation().getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getZ(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getLocation().getZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
|
||||
DataWatcher datawatcher = new DataWatcher();
|
||||
datawatcher.setEntityFlags(flags);
|
||||
datawatcher.setCustomName(displayName, viewer.getVersion());
|
||||
datawatcher.setCustomNameVisible(nameVisible);
|
||||
datawatcher.setArmorStandFlags((byte)16);
|
||||
return datawatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runInEntityScheduler(@NotNull Object entity, @NotNull Runnable task) {
|
||||
((BukkitPlatform)TAB.getInstance().getPlatform()).runEntityTask((Entity) entity, task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDead(@NotNull TabPlayer player) {
|
||||
return ((BukkitTabPlayer)player).getPlayer().isDead();
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* TAB's expansion for PlaceholderAPI
|
||||
*/
|
||||
@Getter
|
||||
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
|
||||
|
||||
/** Map holding all values for all players for easy and high-performance access */
|
||||
@NotNull
|
||||
private final WeakHashMap<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
|
||||
|
||||
/** List of all placeholders offered by the plugin for command suggestions */
|
||||
@NotNull
|
||||
private final List<String> placeholders = Arrays.asList(
|
||||
"%tab_tabprefix%",
|
||||
"%tab_tabsuffix%",
|
||||
"%tab_tagprefix%",
|
||||
"%tab_tagsuffix%",
|
||||
"%tab_customtabname%",
|
||||
"%tab_customtagname%",
|
||||
"%tab_belowname%",
|
||||
"%tab_abovename%",
|
||||
"%tab_tabprefix_raw%",
|
||||
"%tab_tabsuffix_raw%",
|
||||
"%tab_tagprefix_raw%",
|
||||
"%tab_tagsuffix_raw%",
|
||||
"%tab_customtabname_raw%",
|
||||
"%tab_customtagname_raw%",
|
||||
"%tab_belowname_raw%",
|
||||
"%tab_abovename_raw%",
|
||||
"%tab_scoreboard_name%",
|
||||
"%tab_scoreboard_visible%",
|
||||
"%tab_bossbar_visible%",
|
||||
"%tab_nametag_preview%",
|
||||
"%tab_nametag_visibility%",
|
||||
"%tab_replace_<placeholder>%",
|
||||
"%tab_placeholder_<placeholder>%"
|
||||
);
|
||||
|
||||
@Override
|
||||
public boolean persist() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getAuthor() {
|
||||
return TabConstants.PLUGIN_AUTHOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getIdentifier() {
|
||||
return TabConstants.PLUGIN_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getVersion() {
|
||||
return TabConstants.PLUGIN_VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<String> getPlaceholders() {
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String onPlaceholderRequest(@Nullable Player player, @NotNull String identifier) {
|
||||
if (identifier.startsWith("replace_")) {
|
||||
String text = "%" + identifier.substring(8) + "%";
|
||||
String textBefore;
|
||||
do {
|
||||
textBefore = text;
|
||||
for (String placeholder : TAB.getInstance().getPlaceholderManager().detectPlaceholders(text)) {
|
||||
text = text.replace(placeholder, TAB.getInstance().getPlaceholderManager().findReplacement(placeholder,
|
||||
PlaceholderAPI.setPlaceholders(player, placeholder)));
|
||||
}
|
||||
} while (!textBefore.equals(text));
|
||||
return text;
|
||||
}
|
||||
if (identifier.startsWith("placeholder_")) {
|
||||
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TAB.getInstance().getPlaceholderManager());
|
||||
}
|
||||
if (player == null) return "<Player cannot be null>";
|
||||
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
|
||||
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
|
||||
return values.get(p).get(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
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 me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
import me.neznamy.tab.shared.features.types.UnLoadable;
|
||||
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.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Per-world-PlayerList feature handler
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
|
||||
|
||||
/** Config options */
|
||||
private final boolean allowBypass = TAB.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
private final List<String> ignoredWorlds = TAB.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
|
||||
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
|
||||
/**
|
||||
* Constructs new instance and registers events
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance to register events
|
||||
*/
|
||||
public PerWorldPlayerList(JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
checkPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
for (Player pl : BukkitUtils.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
}
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs visibility check on the player. Shows players this player should see and does not,
|
||||
* hides players the player should not see, shows the player to those who should see player and hides
|
||||
* from those, who should not see.
|
||||
*
|
||||
* @param p
|
||||
* Player to update
|
||||
*/
|
||||
private void checkPlayer(@NotNull Player p) {
|
||||
for (Player all : BukkitUtils.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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if viewer should see target player, {@code false} if not.
|
||||
* @param viewer
|
||||
* Player viewing the TabList
|
||||
* @param target
|
||||
* Target displayed in the TabList
|
||||
* @return {@code true} if viewer should see target, {@code false} if not.
|
||||
*/
|
||||
private boolean shouldSee(@NotNull Player viewer, @NotNull 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getFeatureName() {
|
||||
return "Per world PlayerList";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.api.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.features.types.WorldSwitchListener;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
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 org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An additional class with additional code for <1.9 servers due to an entity being required
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
|
||||
|
||||
/**
|
||||
* Constructs new instance and registers events
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance to register events
|
||||
*/
|
||||
public WitherBossBar(JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
//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,
|
||||
featureName, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
super.load();
|
||||
teleport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Wither location for all online players
|
||||
*/
|
||||
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.getPlayers().contains(p)) continue;
|
||||
Location eyeLocation = ((BukkitTabPlayer)p).getPlayer().getEyeLocation();
|
||||
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(60)); // Wither distance
|
||||
if (loc.getY() < 1) loc.setY(1);
|
||||
((BackendTabPlayer)p).getEntityView().teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
TabPlayer player = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player == null) return;
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
|
||||
() -> detectBossBarsAndSend(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWorldChange(@NotNull TabPlayer p, @NotNull String from, @NotNull String to) {
|
||||
for (BossBar line : lineValues) {
|
||||
line.removePlayer(p);
|
||||
}
|
||||
detectBossBarsAndSend(p);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package me.neznamy.tab.platforms.bukkit.hook;
|
||||
|
||||
import me.libraryaddict.disguise.DisguiseAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Class for hooking into LibsDisguises to get disguise status of players.
|
||||
*/
|
||||
public class LibsDisguisesHook {
|
||||
|
||||
/** Flag tracking if LibsDisguises is installed or not */
|
||||
private static boolean installed = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
|
||||
|
||||
/**
|
||||
* Returns {@code true} if LibsDisguises is installed and player is disguised,
|
||||
* {@code false} otherwise.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if disguised, {@code false} otherwise
|
||||
*/
|
||||
public static boolean isDisguised(TabPlayer player) {
|
||||
try {
|
||||
return installed && DisguiseAPI.isDisguised(((BukkitTabPlayer)player).getPlayer());
|
||||
} catch (LinkageError e) {
|
||||
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
|
||||
installed = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class BukkitReflection {
|
||||
|
||||
/** Server's NMS/CraftBukkit package */
|
||||
@Getter
|
||||
private static final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
|
||||
/** Server's minor version */
|
||||
@Getter
|
||||
private static final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
|
||||
/** Flag determining whether this server jar is mojang mapped or not */
|
||||
@Getter
|
||||
private static final boolean mojangMapped = checkMojangMapped();
|
||||
|
||||
/** Flag determining whether the server version is at least 1.19.3 or not */
|
||||
@Getter
|
||||
private static final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
|
||||
/** Flag determining whether the server version is at least 1.19.4 or not */
|
||||
@Getter
|
||||
private static final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
|
||||
|
||||
/** Flag determining whether the server version is at least 1.20.2 or not */
|
||||
@Getter
|
||||
private static final boolean is1_20_2Plus = minorVersion >= 20 && !serverPackage.equals("v1_20_R1");
|
||||
|
||||
private static boolean checkMojangMapped() {
|
||||
try {
|
||||
Class.forName("net.minecraft.network.syncher.SynchedEntityData").getMethod("define",
|
||||
Class.forName("net.minecraft.network.syncher.EntityDataAccessor"), Object.class);
|
||||
return true;
|
||||
} catch (ReflectiveOperationException | NullPointerException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class with given potential names in same order
|
||||
*
|
||||
* @param names
|
||||
* possible class names
|
||||
* @return class for specified names
|
||||
* @throws ClassNotFoundException
|
||||
* if class does not exist
|
||||
*/
|
||||
public static Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
|
||||
for (String name : names) {
|
||||
try {
|
||||
return getLegacyClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
//not the first class name in array
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class from given name. Supports modded servers, such as Thermos.
|
||||
*
|
||||
* @param name
|
||||
* class name
|
||||
* @return class from given name
|
||||
* @throws ClassNotFoundException
|
||||
* if class was not found
|
||||
*/
|
||||
public static Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
|
||||
try {
|
||||
return BukkitReflection.class.getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
} catch (NullPointerException e) {
|
||||
// nested class not found
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.*;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcher class to make work with it much easier
|
||||
*/
|
||||
@ToString
|
||||
public class DataWatcher implements EntityData {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> DataWatcher;
|
||||
private static Constructor<?> newDataWatcher;
|
||||
private static Method DataWatcher_register;
|
||||
private static Method DataWatcher_markDirty;
|
||||
public static Method DataWatcher_packDirty;
|
||||
|
||||
private static Class<?> DataWatcherObject;
|
||||
private static Constructor<?> newDataWatcherObject;
|
||||
|
||||
private static Class<?> DataWatcherRegistry;
|
||||
private static Class<?> DataWatcherSerializer;
|
||||
private static Object DataWatcherSerializer_BYTE;
|
||||
private static Object DataWatcherSerializer_FLOAT;
|
||||
private static Object DataWatcherSerializer_STRING;
|
||||
private static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
|
||||
private static Object DataWatcherSerializer_BOOLEAN;
|
||||
|
||||
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
|
||||
|
||||
/** Watched data */
|
||||
private final Map<Integer, Item> dataValues = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Returns armor stand flags position based on server version
|
||||
*
|
||||
* @return armor stand flags position based on server version
|
||||
*/
|
||||
private static int getArmorStandFlagsPosition() {
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
//1.17.x, 1.18.x, 1.19.x, 1.20.x
|
||||
return 15;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 15) {
|
||||
//1.15.x, 1.16.x
|
||||
return 14;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 14) {
|
||||
//1.14.x
|
||||
return 13;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 10) {
|
||||
//1.10.x - 1.13.x
|
||||
return 11;
|
||||
} else {
|
||||
//1.8.x - 1.9.x
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
if (BukkitReflection.isMojangMapped()) {
|
||||
DataWatcher = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
|
||||
DataWatcherObject = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
|
||||
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
|
||||
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
|
||||
} else if (minorVersion >= 17) {
|
||||
DataWatcher = Class.forName("net.minecraft.network.syncher.DataWatcher");
|
||||
DataWatcherObject = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
|
||||
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
|
||||
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
|
||||
} else {
|
||||
DataWatcher = BukkitReflection.getLegacyClass("DataWatcher");
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcherObject = BukkitReflection.getLegacyClass("DataWatcherObject");
|
||||
DataWatcherRegistry = BukkitReflection.getLegacyClass("DataWatcherRegistry");
|
||||
DataWatcherSerializer = BukkitReflection.getLegacyClass("DataWatcherSerializer");
|
||||
}
|
||||
}
|
||||
newDataWatcher = ReflectionUtils.getOnlyConstructor(DataWatcher);
|
||||
if (BukkitReflection.isMojangMapped()) {
|
||||
DataWatcher_register = DataWatcher.getMethod("define", DataWatcherObject, Object.class);
|
||||
} else if (minorVersion >= 9) {
|
||||
DataWatcher_register = ReflectionUtils.getMethod(DataWatcher, new String[]{"register", "a", "m_135372_"}, DataWatcherObject, Object.class); // {Bukkit, Bukkit 1.18+, Mohist 1.18.2}
|
||||
} else {
|
||||
DataWatcher_register = ReflectionUtils.getMethod(DataWatcher, new String[]{"func_75682_a", "a"}, int.class, Object.class); // {Thermos 1.7.10, Bukkit}
|
||||
}
|
||||
if (minorVersion >= 19) {
|
||||
if (BukkitReflection.isMojangMapped()) {
|
||||
DataWatcher_packDirty = DataWatcher.getMethod("packDirty");
|
||||
} else {
|
||||
DataWatcher_packDirty = DataWatcher.getMethod("b");
|
||||
}
|
||||
}
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
DataWatcher_markDirty = ReflectionUtils.getOnlyMethod(DataWatcher, void.class, DataWatcherObject);
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
newDataWatcherObject = DataWatcherObject.getConstructor(int.class, DataWatcherSerializer);
|
||||
DataWatcherSerializer_BYTE = ReflectionUtils.getField(DataWatcherRegistry, "BYTE", "a", "f_135027_").get(null); // Mohist 1.18.2
|
||||
DataWatcherSerializer_FLOAT = ReflectionUtils.getField(DataWatcherRegistry, "FLOAT", "c", "f_135029_").get(null); // Mohist 1.18.2
|
||||
DataWatcherSerializer_STRING = ReflectionUtils.getField(DataWatcherRegistry, "STRING", "d", "f_135030_").get(null); // Mohist 1.18.2
|
||||
if (BukkitReflection.isMojangMapped()) {
|
||||
DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherRegistry.getDeclaredField("OPTIONAL_COMPONENT").get(null);
|
||||
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("BOOLEAN").get(null);
|
||||
} else {
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherRegistry.getDeclaredField("g").get(null);
|
||||
if (BukkitReflection.is1_19_4Plus()) {
|
||||
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("k").get(null);
|
||||
} else {
|
||||
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("j").get(null);
|
||||
}
|
||||
} else {
|
||||
if (minorVersion >= 13) {
|
||||
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(DataWatcherRegistry, "f", "f_135032_").get(null); // Mohist 1.18.2
|
||||
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry, "i", "f_135035_").get(null); // Mohist 1.18.2
|
||||
} else {
|
||||
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("h").get(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value into data values
|
||||
*
|
||||
* @param position
|
||||
* data position
|
||||
* @param serializer
|
||||
* Object serializer (1.9+)
|
||||
* @param value
|
||||
* value
|
||||
*/
|
||||
public void setValue(int position, @Nullable Object serializer, @NotNull Object value) {
|
||||
dataValues.put(position, new Item(position, serializer, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity byte flags
|
||||
*
|
||||
* @param flags
|
||||
* flags to write
|
||||
*/
|
||||
public void setEntityFlags(byte flags) {
|
||||
setValue(0, DataWatcherSerializer_BYTE, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
|
||||
*
|
||||
* @param customName
|
||||
* target custom name
|
||||
* @param clientVersion
|
||||
* client version
|
||||
*/
|
||||
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT,
|
||||
Optional.of(TAB.getInstance().getPlatform().toComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
setValue(2, DataWatcherSerializer_STRING, customName);
|
||||
} else {
|
||||
//name length is limited to 64 characters on <1.8
|
||||
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
|
||||
if (BukkitReflection.getMinorVersion() >= 6) {
|
||||
setValue(10, null, cutName);
|
||||
} else {
|
||||
setValue(5, null, cutName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes custom name visibility boolean
|
||||
*
|
||||
* @param visible
|
||||
* if visible or not
|
||||
*/
|
||||
public void setCustomNameVisible(boolean visible) {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
setValue(3, DataWatcherSerializer_BOOLEAN, visible);
|
||||
} else {
|
||||
setValue(3, null, (byte)(visible?1:0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity health
|
||||
*
|
||||
* @param health
|
||||
* health of entity
|
||||
*/
|
||||
public void setHealth(float health) {
|
||||
if (BukkitReflection.getMinorVersion() >= 6) {
|
||||
setValue(6, DataWatcherSerializer_FLOAT, health);
|
||||
} else {
|
||||
setValue(16, null, (int)health);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes armor stand flags
|
||||
*
|
||||
* @param flags
|
||||
* flags to write
|
||||
*/
|
||||
public void setArmorStandFlags(byte flags) {
|
||||
setValue(armorStandFlagsPosition, DataWatcherSerializer_BYTE, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes wither invulnerable time
|
||||
* @param time
|
||||
* Time, apparently
|
||||
*/
|
||||
public void setWitherInvulnerableTime(int time) {
|
||||
if (BukkitReflection.getMinorVersion() > 8)
|
||||
throw new UnsupportedOperationException("Not supported on 1.9+");
|
||||
setValue(20, null, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the class into an instance of NMS.DataWatcher
|
||||
*
|
||||
* @return an instance of NMS.DataWatcher with same data
|
||||
*/
|
||||
@SneakyThrows
|
||||
public @NotNull Object build() {
|
||||
Object nmsWatcher;
|
||||
if (newDataWatcher.getParameterCount() == 1) { //1.7+
|
||||
nmsWatcher = newDataWatcher.newInstance(new Object[] {null});
|
||||
} else {
|
||||
nmsWatcher = newDataWatcher.newInstance();
|
||||
}
|
||||
for (Item item : dataValues.values()) {
|
||||
Object nmsObject = item.createObject();
|
||||
DataWatcher_register.invoke(nmsWatcher, nmsObject, item.getValue());
|
||||
if (BukkitReflection.is1_19_3Plus()) DataWatcher_markDirty.invoke(nmsWatcher, nmsObject);
|
||||
}
|
||||
return nmsWatcher;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public static class Item {
|
||||
|
||||
/** Item position */
|
||||
private final int position;
|
||||
|
||||
/** Item serializer (1.9+) */
|
||||
@Nullable
|
||||
private final Object serializer;
|
||||
|
||||
/** Item value */
|
||||
@NotNull
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates data watcher object with position and serializer
|
||||
*
|
||||
* @return NMS data watcher object
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object createObject() {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
return newDataWatcherObject.newInstance(position, serializer);
|
||||
} else {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ComponentCache;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class holding all NMS classes, methods, fields and constructors used by TAB.
|
||||
*/
|
||||
public class NMSStorage {
|
||||
|
||||
/** Instance of this class */
|
||||
@Getter @Setter private static NMSStorage instance;
|
||||
|
||||
/** Server's NMS/CraftBukkit package */
|
||||
private final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
|
||||
/** Server's minor version */
|
||||
@Getter private final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
|
||||
/** Basic universal values */
|
||||
protected Class<?> Packet;
|
||||
protected Class<?> NetworkManager;
|
||||
public Class<?> EntityPlayer;
|
||||
protected Class<?> EntityHuman;
|
||||
protected Class<?> PlayerConnection;
|
||||
|
||||
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
|
||||
public Field PING;
|
||||
|
||||
public Field PLAYER_CONNECTION;
|
||||
public Field NETWORK_MANAGER;
|
||||
public Field CHANNEL;
|
||||
public Method getHandle;
|
||||
public Method sendPacket;
|
||||
public Method getProfile;
|
||||
|
||||
/** Chat components */
|
||||
protected Class<?> ChatSerializer;
|
||||
public Method ChatSerializer_DESERIALIZE;
|
||||
|
||||
public final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
|
||||
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load all fields, methods and constructors.
|
||||
*/
|
||||
@SneakyThrows
|
||||
public NMSStorage() {
|
||||
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
|
||||
loadClasses();
|
||||
if (minorVersion >= 7) {
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
|
||||
} else {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
|
||||
}
|
||||
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
|
||||
} else {
|
||||
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
|
||||
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
try {
|
||||
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
|
||||
} catch (IllegalStateException catServer) {
|
||||
getProfile = ReflectionUtils.getMethod(EntityHuman, new String[] {"getProfile"});
|
||||
}
|
||||
}
|
||||
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
|
||||
BukkitTabList.load();
|
||||
DataWatcher.load();
|
||||
PacketEntityView.load();
|
||||
PacketScoreboard.load();
|
||||
if (minorVersion < 17) {
|
||||
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
|
||||
}
|
||||
}
|
||||
|
||||
private void loadClasses() throws ClassNotFoundException {
|
||||
if (minorVersion >= 17 && ReflectionUtils.classExists("net.minecraft.ChatFormatting")) {
|
||||
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
|
||||
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
|
||||
NetworkManager = Class.forName("net.minecraft.network.Connection");
|
||||
Packet = Class.forName("net.minecraft.network.protocol.Packet");
|
||||
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
|
||||
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
|
||||
} else if (minorVersion >= 17) {
|
||||
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
|
||||
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
|
||||
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
|
||||
Packet = Class.forName("net.minecraft.network.protocol.Packet");
|
||||
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
|
||||
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
|
||||
} else {
|
||||
EntityHuman = BukkitReflection.getLegacyClass("EntityHuman");
|
||||
Packet = BukkitReflection.getLegacyClass("Packet");
|
||||
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
|
||||
PlayerConnection = BukkitReflection.getLegacyClass("PlayerConnection");
|
||||
NetworkManager = BukkitReflection.getLegacyClass("NetworkManager");
|
||||
if (minorVersion >= 7) {
|
||||
ChatSerializer = BukkitReflection.getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.backend.entityview.EntityView;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@SuppressWarnings("unchecked")
|
||||
public class PacketEntityView implements EntityView {
|
||||
|
||||
/** PacketPlayOutEntityDestroy */
|
||||
private static Class<?> EntityDestroyClass;
|
||||
private static Constructor<?> newEntityDestroy;
|
||||
private static Field EntityDestroy_Entities;
|
||||
|
||||
/** PacketPlayOutEntityMetadata */
|
||||
private static Constructor<?> newEntityMetadata;
|
||||
|
||||
/** PacketPlayOutEntityTeleport */
|
||||
private static Class<?> EntityTeleportClass;
|
||||
private static Constructor<?> newEntityTeleport;
|
||||
private static Field EntityTeleport_EntityId;
|
||||
private static Field EntityTeleport_X;
|
||||
private static Field EntityTeleport_Y;
|
||||
private static Field EntityTeleport_Z;
|
||||
|
||||
/** PacketPlayOutSpawnEntityLiving */
|
||||
private static Class<?> SpawnEntityClass;
|
||||
private static Constructor<?> newSpawnEntity;
|
||||
|
||||
/** 1.17+ */
|
||||
private static Object Vec3D_Empty;
|
||||
private static Object EntityTypes_ARMOR_STAND;
|
||||
|
||||
/** 1.16.5- */
|
||||
private static Field SpawnEntity_EntityId;
|
||||
private static Field SpawnEntity_EntityType;
|
||||
private static Field SpawnEntity_UUID;
|
||||
private static Field SpawnEntity_X;
|
||||
private static Field SpawnEntity_Y;
|
||||
private static Field SpawnEntity_Z;
|
||||
private static Field SpawnEntity_DataWatcher;
|
||||
private static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
|
||||
|
||||
/** Other entity packets */
|
||||
private static Class<?> PacketPlayOutEntity;
|
||||
private static Field PacketPlayOutEntity_ENTITYID;
|
||||
private static Class<?> PacketPlayOutEntityLook;
|
||||
private static Class<?> PacketPlayOutNamedEntitySpawn;
|
||||
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
|
||||
|
||||
private static Object dummyEntity;
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
/** Player this view belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
/**
|
||||
* Loads all required classes and fields and throws Exception if something went wrong
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
loadEntityMetadata();
|
||||
loadEntityDestroy();
|
||||
loadEntityTeleport();
|
||||
loadEntityMove();
|
||||
loadEntitySpawn();
|
||||
available = true;
|
||||
}
|
||||
|
||||
private static void loadEntityMetadata() throws ReflectiveOperationException {
|
||||
// Class
|
||||
Class<?> entityMetadataClass = getClass("network.protocol.game.ClientboundSetEntityDataPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
|
||||
|
||||
// Constructor
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
newEntityMetadata = entityMetadataClass.getConstructor(int.class, List.class);
|
||||
} else {
|
||||
newEntityMetadata = entityMetadataClass.getConstructor(int.class, DataWatcher.DataWatcher, boolean.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadEntityDestroy() throws ReflectiveOperationException {
|
||||
// Class
|
||||
EntityDestroyClass = getClass("network.protocol.game.ClientboundRemoveEntitiesPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
|
||||
|
||||
// Constructor
|
||||
try {
|
||||
newEntityDestroy = EntityDestroyClass.getConstructor(int[].class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
//1.17.0
|
||||
newEntityDestroy = EntityDestroyClass.getConstructor(int.class);
|
||||
}
|
||||
|
||||
// Field
|
||||
EntityDestroy_Entities = ReflectionUtils.getOnlyField(EntityDestroyClass);
|
||||
}
|
||||
|
||||
private static void loadEntityTeleport() throws ReflectiveOperationException {
|
||||
// Class
|
||||
EntityTeleportClass = getClass("network.protocol.game.ClientboundTeleportEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
|
||||
|
||||
// Constructor
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
newEntityTeleport = EntityTeleportClass.getConstructor(getClass("world.entity.Entity"));
|
||||
} else {
|
||||
newEntityTeleport = EntityTeleportClass.getConstructor();
|
||||
}
|
||||
|
||||
// Fields
|
||||
EntityTeleport_EntityId = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(0);
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(0);
|
||||
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(1);
|
||||
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(2);
|
||||
} else {
|
||||
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(1);
|
||||
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(2);
|
||||
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(3);
|
||||
}
|
||||
|
||||
// Dummy armor stand for constructor
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
Class<?> world = getClass("world.level.Level", "world.level.World", "World");
|
||||
Class<?> entityArmorStand = getClass("world.entity.decoration.ArmorStand",
|
||||
"world.entity.decoration.EntityArmorStand", "EntityArmorStand");
|
||||
Constructor<?> newEntityArmorStand = entityArmorStand.getConstructor(world, double.class, double.class, double.class);
|
||||
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".CraftWorld").getMethod("getHandle");
|
||||
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadEntityMove() throws ReflectiveOperationException {
|
||||
// Classes
|
||||
PacketPlayOutEntity = getClass("network.protocol.game.ClientboundMoveEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
|
||||
PacketPlayOutEntityLook = getClass("network.protocol.game.ClientboundMoveEntityPacket$Rot",
|
||||
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook",
|
||||
"PacketPlayOutEntityLook", "Packet32EntityLook");
|
||||
|
||||
// Field
|
||||
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
|
||||
}
|
||||
|
||||
private static void loadEntitySpawn() throws ReflectiveOperationException {
|
||||
SpawnEntityClass = getClass("network.protocol.game.ClientboundAddEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutSpawnEntity", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
|
||||
SpawnEntity_EntityId = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(0);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
Class<?> Vec3D = getClass("world.phys.Vec3", "world.phys.Vec3D");
|
||||
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
|
||||
Class<?> EntityTypes = getClass("world.entity.EntityType", "world.entity.EntityTypes");
|
||||
if (BukkitReflection.isMojangMapped()) {
|
||||
EntityTypes_ARMOR_STAND = EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
EntityTypes_ARMOR_STAND = EntityTypes.getDeclaredField("d").get(null);
|
||||
} else {
|
||||
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "c", "f_20529_").get(null); // Mohist 1.18.2
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
|
||||
} else {
|
||||
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
|
||||
}
|
||||
} else {
|
||||
newSpawnEntity = SpawnEntityClass.getConstructor();
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
SpawnEntity_UUID = ReflectionUtils.getOnlyField(SpawnEntityClass, UUID.class);
|
||||
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(0);
|
||||
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(1);
|
||||
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(2);
|
||||
} else {
|
||||
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(2);
|
||||
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(3);
|
||||
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(4);
|
||||
}
|
||||
SpawnEntity_EntityType = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(1);
|
||||
if (BukkitReflection.getMinorVersion() <= 14) {
|
||||
SpawnEntity_DataWatcher = ReflectionUtils.getOnlyField(SpawnEntityClass, DataWatcher.DataWatcher);
|
||||
}
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 1);
|
||||
} else {
|
||||
entityIds.put(EntityType.WITHER, 64);
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 30);
|
||||
}
|
||||
}
|
||||
if (!BukkitReflection.is1_20_2Plus()) {
|
||||
PacketPlayOutNamedEntitySpawn = getClass("network.protocol.game.ClientboundAddPlayerPacket",
|
||||
"network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
|
||||
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static int floor(double paramDouble) {
|
||||
int i = (int)paramDouble;
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location l, @NotNull EntityData data) {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
if (minorVersion >= 19) {
|
||||
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0d));
|
||||
} else if (minorVersion >= 17) {
|
||||
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty));
|
||||
} else {
|
||||
Object nmsPacket = newSpawnEntity.newInstance();
|
||||
SpawnEntity_EntityId.set(nmsPacket, entityId);
|
||||
if (minorVersion <= 14) {
|
||||
SpawnEntity_DataWatcher.set(nmsPacket, data.build());
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
SpawnEntity_UUID.set(nmsPacket, id);
|
||||
SpawnEntity_X.set(nmsPacket, l.getX());
|
||||
SpawnEntity_Y.set(nmsPacket, l.getY());
|
||||
SpawnEntity_Z.set(nmsPacket, l.getZ());
|
||||
} else {
|
||||
SpawnEntity_X.set(nmsPacket, floor(l.getX()*32));
|
||||
SpawnEntity_Y.set(nmsPacket, floor(l.getY()*32));
|
||||
SpawnEntity_Z.set(nmsPacket, floor(l.getZ()*32));
|
||||
}
|
||||
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) entityType));
|
||||
player.sendPacket(nmsPacket);
|
||||
}
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
|
||||
updateEntityMetadata(entityId, data);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
if (newEntityMetadata.getParameterCount() == 2) {
|
||||
//1.19.3+
|
||||
player.sendPacket(newEntityMetadata.newInstance(entityId, DataWatcher.DataWatcher_packDirty.invoke(data.build())));
|
||||
} else {
|
||||
player.sendPacket(newEntityMetadata.newInstance(entityId, data.build(), true));
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void teleportEntity(int entityId, @NotNull Location location) {
|
||||
Object nmsPacket;
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
nmsPacket = newEntityTeleport.newInstance(dummyEntity);
|
||||
} else {
|
||||
nmsPacket = newEntityTeleport.newInstance();
|
||||
}
|
||||
EntityTeleport_EntityId.set(nmsPacket, entityId);
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
EntityTeleport_X.set(nmsPacket, location.getX());
|
||||
EntityTeleport_Y.set(nmsPacket, location.getY());
|
||||
EntityTeleport_Z.set(nmsPacket, location.getZ());
|
||||
} else {
|
||||
EntityTeleport_X.set(nmsPacket, floor(location.getX()*32));
|
||||
EntityTeleport_Y.set(nmsPacket, floor(location.getY()*32));
|
||||
EntityTeleport_Z.set(nmsPacket, floor(location.getZ()*32));
|
||||
}
|
||||
player.sendPacket(nmsPacket);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void destroyEntities(int... entities) {
|
||||
if (newEntityDestroy.getParameterTypes()[0] != int.class) {
|
||||
player.sendPacket(newEntityDestroy.newInstance(new Object[]{entities}));
|
||||
} else {
|
||||
//1.17.0 Mojank
|
||||
for (int entity : entities) {
|
||||
player.sendPacket(newEntityDestroy.newInstance(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyPacket(@NotNull Object packet) {
|
||||
return EntityDestroyClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeleportPacket(@NotNull Object packet) {
|
||||
return EntityTeleportClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
return SpawnEntityClass.isInstance(packet);
|
||||
} else {
|
||||
return PacketPlayOutNamedEntitySpawn.isInstance(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMovePacket(@NotNull Object packet) {
|
||||
return PacketPlayOutEntity.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLookPacket(@NotNull Object packet) {
|
||||
return PacketPlayOutEntityLook.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getTeleportEntityId(@NotNull Object teleportPacket) {
|
||||
return EntityTeleport_EntityId.getInt(teleportPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getMoveEntityId(@NotNull Object movePacket) {
|
||||
return PacketPlayOutEntity_ENTITYID.getInt(movePacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
return SpawnEntity_EntityId.getInt(playerSpawnPacket);
|
||||
} else {
|
||||
return PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(playerSpawnPacket);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
|
||||
Object entities = PacketEntityView.EntityDestroy_Entities.get(destroyPacket);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
if (entities instanceof List) {
|
||||
return ((List<Integer>)entities).stream().mapToInt(i -> i).toArray();
|
||||
} else {
|
||||
//1.17.0
|
||||
return new int[]{(int) entities};
|
||||
}
|
||||
} else {
|
||||
return (int[]) entities;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class from given potential names. Supports modded servers, such as Thermos.
|
||||
*
|
||||
* @param names
|
||||
* Potential class names
|
||||
* @return class from given name
|
||||
* @throws ClassNotFoundException
|
||||
* if class was not found
|
||||
*/
|
||||
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
|
||||
for (String name : names) {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft." + name);
|
||||
} else {
|
||||
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft.server." + BukkitReflection.getServerPackage() + "." + name);
|
||||
}
|
||||
} catch (ClassNotFoundException | NullPointerException e) {
|
||||
// Wrong class name
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException("No class found with potential names " + Arrays.toString(names));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package me.neznamy.tab.platforms.bukkit.platform;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.*;
|
||||
import me.neznamy.tab.shared.GroupManager;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitNameTagX;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
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.hook.LuckPermsHook;
|
||||
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Implementation of Platform interface for Bukkit platform
|
||||
*/
|
||||
@Getter
|
||||
public class BukkitPlatform implements BackendPlatform<Object> {
|
||||
|
||||
/** Plugin instance for registering tasks and events */
|
||||
@NotNull
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
/** Variables checking presence of other plugins to hook into */
|
||||
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
|
||||
|
||||
/** NMS server to get TPS from on spigot */
|
||||
@Nullable private Object server;
|
||||
|
||||
/** TPS field */
|
||||
@Nullable private Field spigotTps;
|
||||
|
||||
/** Detection for presence of Paper's TPS getter */
|
||||
private final boolean paperTps = ReflectionUtils.methodExists(Bukkit.class, "getTPS");
|
||||
|
||||
/** Detection for presence of Paper's MSPT getter */
|
||||
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
|
||||
|
||||
public BukkitPlatform(@NotNull JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
try {
|
||||
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
spigotTps = server.getClass().getField("recentTps");
|
||||
} catch (ReflectiveOperationException e) {
|
||||
//not spigot
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BossBarManagerImpl getLegacyBossBar() {
|
||||
return new WitherBossBar(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
TAB.getInstance().addPlayer(new BukkitTabPlayer(this, p));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerPlaceholders() {
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
|
||||
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
|
||||
if (rspChat != null) {
|
||||
Chat chat = rspChat.getProvider();
|
||||
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
|
||||
}
|
||||
}
|
||||
// Override for the PAPI placeholder to prevent console errors on unsupported server versions when ping field changes
|
||||
manager.registerPlayerPlaceholder("%player_ping%", manager.getRefreshInterval("%player_ping%"),
|
||||
p -> ((TabPlayer)p).getPing());
|
||||
BackendPlatform.super.registerPlaceholders();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public NameTag getUnlimitedNameTags() {
|
||||
return new BukkitNameTagX(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabExpansion createTabExpansion() {
|
||||
if (placeholderAPI) {
|
||||
BukkitTabExpansion expansion = new BukkitTabExpansion();
|
||||
expansion.register();
|
||||
return expansion;
|
||||
}
|
||||
return new EmptyTabExpansion();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TabFeature getPerWorldPlayerList() {
|
||||
return new PerWorldPlayerList(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
if (!placeholderAPI) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
|
||||
return;
|
||||
}
|
||||
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
|
||||
int refresh = pl.getRefreshInterval(identifier);
|
||||
if (identifier.startsWith("%rel_")) {
|
||||
//relational placeholder
|
||||
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRefreshInterval(identifier), (viewer, target) ->
|
||||
PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier));
|
||||
} else if (identifier.startsWith("%sync:")) {
|
||||
registerSyncPlaceholder(identifier, refresh);
|
||||
} else if (identifier.startsWith("%server_")) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh,
|
||||
() -> PlaceholderAPI.setPlaceholders(null, identifier));
|
||||
} else {
|
||||
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh,
|
||||
p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
|
||||
}
|
||||
}
|
||||
|
||||
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
|
||||
String syncedPlaceholder = "%" + identifier.substring(6);
|
||||
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
|
||||
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
long time = System.nanoTime();
|
||||
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull IChatBaseComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + BukkitUtils.toBukkitFormat(message, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull IChatBaseComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] [WARN] " + BukkitUtils.toBukkitFormat(message, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[Bukkit] " + Bukkit.getName() + " - " +
|
||||
Bukkit.getBukkitVersion().split("-")[0] +
|
||||
" (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener() {
|
||||
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommand() {
|
||||
PluginCommand command = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
|
||||
if (command != null) {
|
||||
BukkitTabCommand cmd = new BukkitTabCommand();
|
||||
command.setExecutor(cmd);
|
||||
command.setTabCompleter(cmd);
|
||||
} else {
|
||||
logWarn(new IChatBaseComponent("Failed to register command, is it defined in plugin.yml?"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
Metrics metrics = new Metrics(plugin, 5304);
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED,
|
||||
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
|
||||
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION,
|
||||
() -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ProtocolVersion getServerVersion() {
|
||||
return ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
|
||||
return NMSStorage.getInstance().componentCache.get(component, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public GroupManager detectPermissionPlugin() {
|
||||
if (LuckPermsHook.getInstance().isInstalled()) {
|
||||
return new GroupManager("LuckPerms", LuckPermsHook.getInstance().getGroupFunction());
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
|
||||
if (provider != null && !provider.getProvider().getName().equals("SuperPerms")) {
|
||||
return new GroupManager(provider.getProvider().getName(), p -> provider.getProvider().getPrimaryGroup((Player) p.getPlayer()));
|
||||
}
|
||||
}
|
||||
return new GroupManager("None", p -> TabConstants.NO_GROUP);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public double getTPS() {
|
||||
if (paperTps) {
|
||||
return Bukkit.getTPS()[0];
|
||||
} else if (spigotTps != null) {
|
||||
return ((double[]) spigotTps.get(server))[0];
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMSPT() {
|
||||
if (paperMspt) return Bukkit.getAverageTickTime();
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package me.neznamy.tab.platforms.bukkit.platform;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Platform override for Folia
|
||||
*/
|
||||
public class FoliaPlatform extends BukkitPlatform {
|
||||
|
||||
public FoliaPlatform(@NotNull JavaPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
super.loadPlayers();
|
||||
|
||||
// Values are never updated in the API, warn users
|
||||
logWarn(new IChatBaseComponent("Folia never updates MSPT and TPS values in the API, making " +
|
||||
"%mspt% and %tps% return the default values (0 and 20)."));
|
||||
|
||||
// Folia never calls PlayerChangedWorldEvent, this is a workaround
|
||||
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100, "Folia compatibility", "Refreshing world", () -> {
|
||||
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
|
||||
String bukkitWorld = ((Player)player.getPlayer()).getWorld().getName();
|
||||
if (!player.getWorld().equals(bukkitWorld)) {
|
||||
TAB.getInstance().getFeatureManager().onWorldChange(player.getUniqueId(), bukkitWorld);
|
||||
PerWorldPlayerList pwp = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST);
|
||||
if (pwp != null) {
|
||||
runSync((Entity) player.getPlayer(), () -> pwp.onWorldChange(new PlayerChangedWorldEvent((Player) player.getPlayer(), ((Player) player.getPlayer()).getWorld())));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
|
||||
String syncedPlaceholder = "%" + identifier.substring(6);
|
||||
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
|
||||
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
|
||||
runSync((Entity) p.getPlayer(), () -> {
|
||||
long time = System.nanoTime();
|
||||
String output = isPlaceholderAPI() ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier;
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
|
||||
TAB.getInstance().getCPUManager().runTask(() -> ppl[0].updateValue(p, output)); // To ensure player is loaded
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs task using player's entity scheduler. It's using reflection, because
|
||||
* Folia uses Java 17 while TAB maintains Java 8 compatibility for compatibility
|
||||
* with MC versions older than their player base.
|
||||
*
|
||||
* @param entity
|
||||
* entity to run task for
|
||||
* @param task
|
||||
* Task to run
|
||||
*/
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("JavaReflectionMemberAccess")
|
||||
private void runSync(@NotNull Entity entity, @NotNull Runnable task) {
|
||||
Object entityScheduler = Entity.class.getMethod("getScheduler").invoke(entity);
|
||||
Consumer<?> consumer = $ -> task.run(); // Reflection and lambdas don't go together
|
||||
entityScheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
|
||||
.invoke(entityScheduler, getPlugin(), consumer, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
|
||||
runSync(entity, task);
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.scoreboard.NameTagVisibility;
|
||||
import org.bukkit.scoreboard.Objective;
|
||||
import org.bukkit.scoreboard.RenderType;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using Bukkit API (1.5.2+). It has several issues:
|
||||
* #1 - Limitations on legacy versions are forced in the API.
|
||||
* While this may not seem like a problem, it enforces those
|
||||
* limits even for 1.13+ players (if using ViaVersion).<p>
|
||||
* #2 - Modern versions no longer have any limits, but md_5
|
||||
* decided to add some random limits for absolutely no reason
|
||||
* at all. Scoreboard title received a random 128 characters
|
||||
* limit including color codes. Together with the almighty bukkit
|
||||
* RGB format using 14 characters for 1 color code, this makes
|
||||
* gradients just impossible to use. Team prefix/suffix also
|
||||
* received a 64 characters limit (excluding color codes at least),
|
||||
* however that might not be enough for displaying a line of text
|
||||
* in sidebar, which would require splitting the text into prefix
|
||||
* and suffix, which is just begging for bugs to be introduced.<p>
|
||||
* #3 - Other plugins can decide to put players into their own
|
||||
* scoreboard, automatically destroying all visuals made by the
|
||||
* plugin. They might also put all players into the same scoreboard,
|
||||
* making per-player view of teams, especially sidebar not working.<p>
|
||||
*/
|
||||
// Throw the NPE if something is not as expected, parent class should ensure it anyway
|
||||
@SuppressWarnings({"ConstantConditions", "deprecation"})
|
||||
public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
|
||||
|
||||
/** Flag tracking whether this implementation is available for use */
|
||||
@Getter
|
||||
private static final boolean available = ReflectionUtils.methodExists(Bukkit.class, "getScoreboardManager");
|
||||
|
||||
/** Server's minor version */
|
||||
private final int serverMinorVersion = BukkitReflection.getMinorVersion();
|
||||
|
||||
@NotNull
|
||||
protected final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
|
||||
|
||||
public BukkitScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
|
||||
// Put player into a different scoreboard for per-player view
|
||||
player.getPlayer().setScoreboard(scoreboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getObjective(objective).setDisplaySlot(org.bukkit.scoreboard.DisplaySlot.valueOf(slot.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
checkPlayerScoreboard();
|
||||
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
|
||||
scoreboard.getObjective(objective).getScore(playerName).setScore(score);
|
||||
} else {
|
||||
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(playerName)).setScore(score);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
checkPlayerScoreboard();
|
||||
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
|
||||
scoreboard.resetScores(playerName);
|
||||
} else {
|
||||
scoreboard.resetScores(Bukkit.getOfflinePlayer(playerName));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
checkPlayerScoreboard();
|
||||
newObjective(objectiveName, "dummy", title, display);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getObjective(objectiveName).unregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
checkPlayerScoreboard();
|
||||
Objective obj = scoreboard.getObjective(objectiveName);
|
||||
setDisplayName(obj, title);
|
||||
if (serverMinorVersion >= 14) obj.setRenderType(RenderType.valueOf(display.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
checkPlayerScoreboard();
|
||||
Team team = scoreboard.registerNewTeam(name);
|
||||
setPrefix(team, prefix);
|
||||
setSuffix(team, suffix);
|
||||
if (serverMinorVersion >= 8)
|
||||
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
|
||||
if (serverMinorVersion >= 9)
|
||||
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
|
||||
if (serverMinorVersion >= 13)
|
||||
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
|
||||
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
|
||||
players.forEach(team::addEntry);
|
||||
} else {
|
||||
players.forEach(player -> team.addPlayer(Bukkit.getOfflinePlayer(player)));
|
||||
}
|
||||
team.setAllowFriendlyFire((options & 0x01) != 0);
|
||||
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getTeam(name).unregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
checkPlayerScoreboard();
|
||||
Team team = scoreboard.getTeam(name);
|
||||
setPrefix(team, prefix);
|
||||
setSuffix(team, suffix);
|
||||
if (serverMinorVersion >= 8)
|
||||
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
|
||||
if (serverMinorVersion >= 9)
|
||||
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
|
||||
if (serverMinorVersion >= 13)
|
||||
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
|
||||
team.setAllowFriendlyFire((options & 0x01) != 0);
|
||||
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
|
||||
}
|
||||
|
||||
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
|
||||
if (serverMinorVersion >= 14) {
|
||||
scoreboard.registerNewObjective(objectiveName, criteria, transform(title, 128, 32), RenderType.valueOf(display.name()));
|
||||
} else {
|
||||
setDisplayName(scoreboard.registerNewObjective(objectiveName, display == HealthDisplay.HEARTS ? "health" : "dummy"), title);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
|
||||
objective.setDisplayName(transform(displayName, 128, 32));
|
||||
}
|
||||
|
||||
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
|
||||
team.setPrefix(transform(prefix, 64, 16));
|
||||
}
|
||||
|
||||
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
|
||||
team.setSuffix(transform(suffix, 64, 16));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms text into format using bukkit RGB syntax if server version is 1.16+
|
||||
*
|
||||
* @param text
|
||||
* Text to transform
|
||||
* @param maxLengthModern
|
||||
* Maximum text length defined by bukkit API on 1.13+
|
||||
* @param maxLengthLegacy
|
||||
* Maximum text length defined by bukkit API on 1.12-
|
||||
* @return Converted text
|
||||
*/
|
||||
@NotNull
|
||||
private String transform(@NotNull String text, int maxLengthModern, int maxLengthLegacy) {
|
||||
String transformed = BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(text), player.getVersion().getMinorVersion() >= 16);
|
||||
if (serverMinorVersion >= 16 && maxLengthModern < 128) { // Scoreboard title is not stripping colors
|
||||
while (ChatColor.stripColor(transformed).length() > maxLengthModern)
|
||||
transformed = transformed.substring(0, transformed.length()-1);
|
||||
} else if (serverMinorVersion >= 13) {
|
||||
if (transformed.length() > maxLengthModern)
|
||||
transformed = transformed.substring(0, maxLengthModern);
|
||||
} else {
|
||||
if (transformed.length() > maxLengthLegacy)
|
||||
transformed = transformed.substring(0, maxLengthLegacy);
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure player is in the correct scoreboard.
|
||||
* If not, puts the player into correct scoreboard and prints
|
||||
* a warning.
|
||||
* The only possible reason for this to happen is another plugin
|
||||
* putting a player into a different scoreboard.
|
||||
* Sadly there is no efficient solution to this, which md_5 fails
|
||||
* to understand and keeps saying you don't need packets for scoreboards.
|
||||
*/
|
||||
private void checkPlayerScoreboard() {
|
||||
if (player.getPlayer().getScoreboard() != scoreboard) {
|
||||
player.getPlayer().setScoreboard(scoreboard);
|
||||
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
|
||||
"than expected. This means another plugin changed player's scoreboard.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static me.neznamy.tab.platforms.bukkit.nms.BukkitReflection.getLegacyClass;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation for Bukkit, which uses packets
|
||||
* to send scoreboards to use the full potential on all versions
|
||||
* and server software without any artificial limits.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
private static Object emptyScoreboard;
|
||||
private static Field IScoreboardCriteria_self;
|
||||
|
||||
// PacketPlayOutScoreboardDisplayObjective
|
||||
public static Class<?> DisplayObjectiveClass;
|
||||
private static Constructor<?> newDisplayObjective;
|
||||
public static Field DisplayObjective_POSITION;
|
||||
public static Field DisplayObjective_OBJECTIVE_NAME;
|
||||
public static Enum[] DisplaySlot_values;
|
||||
|
||||
// PacketPlayOutScoreboardScore
|
||||
private static Class<Enum> EnumScoreboardAction;
|
||||
private static Constructor<?> newScorePacket_1_13;
|
||||
private static Constructor<?> newScorePacket_String;
|
||||
private static Constructor<?> newScorePacket;
|
||||
private static Constructor<?> newScoreboardScore;
|
||||
private static Method ScoreboardScore_setScore;
|
||||
|
||||
// PacketPlayOutScoreboardObjective
|
||||
public static Class<?> ObjectivePacketClass;
|
||||
private static Constructor<?> newObjectivePacket;
|
||||
public static Field Objective_OBJECTIVE_NAME;
|
||||
public static Field Objective_METHOD;
|
||||
private static Field Objective_RENDER_TYPE;
|
||||
private static Field Objective_DISPLAY_NAME;
|
||||
private static Class<Enum> EnumScoreboardHealthDisplay;
|
||||
private static Constructor<?> newScoreboardObjective;
|
||||
|
||||
// PacketPlayOutScoreboardTeamStorage
|
||||
public static Class<?> TeamPacketClass;
|
||||
private static Constructor<?> newTeamPacket;
|
||||
private static Method TeamPacketConstructor_of;
|
||||
private static Method TeamPacketConstructor_ofBoolean;
|
||||
public static Field TeamPacket_NAME;
|
||||
public static Field TeamPacket_ACTION;
|
||||
public static Field TeamPacket_PLAYERS;
|
||||
private static Class<Enum> EnumNameTagVisibility;
|
||||
private static Class<Enum> EnumTeamPush;
|
||||
private static Class<Enum> EnumChatFormatClass;
|
||||
private static Constructor<?> newScoreboardTeam;
|
||||
private static Method ScoreboardTeam_getPlayerNameSet;
|
||||
private static Method ScoreboardTeam_setNameTagVisibility;
|
||||
private static Method ScoreboardTeam_setCollisionRule;
|
||||
private static Method ScoreboardTeam_setPrefix;
|
||||
private static Method ScoreboardTeam_setSuffix;
|
||||
private static Method ScoreboardTeam_setColor;
|
||||
private static Method ScoreboardTeam_setAllowFriendlyFire;
|
||||
private static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
|
||||
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Class<?> scoreboardTeam;
|
||||
Class<?> scoreboardObjective;
|
||||
Class<?> scoreboardScoreClass;
|
||||
Class<?> IScoreboardCriteria;
|
||||
Class<?> scoreboard;
|
||||
Class<?> scorePacketClass;
|
||||
Class<?> IChatBaseComponent = null;
|
||||
if (BukkitReflection.isMojangMapped()) {
|
||||
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
|
||||
EnumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
|
||||
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
|
||||
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
|
||||
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
|
||||
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
|
||||
scoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
|
||||
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.Score");
|
||||
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
|
||||
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
|
||||
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
|
||||
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
|
||||
scoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
|
||||
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
|
||||
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
|
||||
} else if (minorVersion >= 17) {
|
||||
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
|
||||
EnumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
|
||||
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
|
||||
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
|
||||
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
|
||||
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
|
||||
scoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
|
||||
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
|
||||
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
|
||||
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
|
||||
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
|
||||
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
|
||||
scoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
|
||||
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
|
||||
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
|
||||
} else {
|
||||
EnumChatFormatClass = (Class<Enum>) getLegacyClass("EnumChatFormat");
|
||||
DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
|
||||
ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
|
||||
TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
|
||||
scorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
|
||||
scoreboard = getLegacyClass("Scoreboard");
|
||||
scoreboardObjective = getLegacyClass("ScoreboardObjective");
|
||||
scoreboardScoreClass = getLegacyClass("ScoreboardScore");
|
||||
IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
|
||||
scoreboardTeam = getLegacyClass("ScoreboardTeam");
|
||||
if (minorVersion >= 7) {
|
||||
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
|
||||
EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
|
||||
EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
|
||||
}
|
||||
}
|
||||
emptyScoreboard = scoreboard.getConstructor().newInstance();
|
||||
IScoreboardCriteria_self = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
Class<?> DisplaySlot = Class.forName("net.minecraft.world.scores.DisplaySlot");
|
||||
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, scoreboardObjective);
|
||||
DisplaySlot_values = (Enum[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
|
||||
} else {
|
||||
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, scoreboardObjective);
|
||||
}
|
||||
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
|
||||
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(scoreboardObjective);
|
||||
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
|
||||
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
|
||||
Objective_METHOD = list.get(list.size()-1);
|
||||
newScoreboardScore = scoreboardScoreClass.getConstructor(scoreboard, scoreboardObjective, String.class);
|
||||
newScoreboardTeam = scoreboardTeam.getConstructor(scoreboard, String.class);
|
||||
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
|
||||
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
|
||||
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
|
||||
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
|
||||
if (minorVersion >= 13) {
|
||||
newScorePacket_1_13 = scorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor(scoreboardObjective, int.class);
|
||||
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, IChatBaseComponent);
|
||||
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumChatFormatClass);
|
||||
} else {
|
||||
newScorePacket_String = scorePacketClass.getConstructor(String.class);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor();
|
||||
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
|
||||
if (minorVersion >= 8) {
|
||||
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass);
|
||||
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
|
||||
} else {
|
||||
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass, int.class);
|
||||
}
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumTeamPush);
|
||||
}
|
||||
if (minorVersion >= 17) {
|
||||
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
|
||||
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
|
||||
} else {
|
||||
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
|
||||
}
|
||||
if (BukkitReflection.isMojangMapped()) {
|
||||
ScoreboardScore_setScore = scoreboardScoreClass.getMethod("setScore", int.class);
|
||||
ScoreboardTeam_setAllowFriendlyFire = scoreboardTeam.getMethod("setAllowFriendlyFire", boolean.class);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles = scoreboardTeam.getMethod("setSeeFriendlyInvisibles", boolean.class);
|
||||
ScoreboardTeam_setPrefix = scoreboardTeam.getMethod("setPlayerPrefix", IChatBaseComponent);
|
||||
ScoreboardTeam_setSuffix = scoreboardTeam.getMethod("setPlayerSuffix", IChatBaseComponent);
|
||||
ScoreboardTeam_setNameTagVisibility = scoreboardTeam.getMethod("setNameTagVisibility", EnumNameTagVisibility);
|
||||
} else {
|
||||
ScoreboardScore_setScore = ReflectionUtils.getMethod(scoreboardScoreClass, new String[] {"func_96647_c", "setScore", "b", "c", "m_83402_"}, int.class); // {Thermos, 1.5.1 - 1.17.1, 1.18+, 1.5, Mohist 1.18.2}
|
||||
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(scoreboardTeam, new String[] {"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, boolean.class); // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(scoreboardTeam, new String[] {"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_"}, boolean.class); // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
|
||||
if (minorVersion >= 13) {
|
||||
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(scoreboardTeam, new String[]{"setPrefix", "b", "m_83360_"}, IChatBaseComponent); // {1.17.1-, 1.18+, Mohist 1.18.2}
|
||||
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(scoreboardTeam, new String[]{"setSuffix", "c", "m_83365_"}, IChatBaseComponent); // {1.17.1-, 1.18+, Mohist 1.18.2}
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(scoreboardTeam, new String[] {"func_96666_b", "setPrefix", "b"}, String.class); // {Thermos, 1.5.1+, 1.5}
|
||||
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(scoreboardTeam, new String[] {"func_96662_c", "setSuffix", "c"}, String.class); // {Thermos, 1.5.1+, 1.5}
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(scoreboardTeam, new String[] {"setNameTagVisibility", "a", "m_83346_"}, EnumNameTagVisibility); // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
|
||||
}
|
||||
}
|
||||
available = true;
|
||||
}
|
||||
|
||||
public PacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
Object displaySlot;
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
displaySlot = DisplaySlot_values[slot.ordinal()];
|
||||
} else {
|
||||
displaySlot = slot.ordinal();
|
||||
}
|
||||
player.sendPacket(newDisplayObjective.newInstance(displaySlot, newScoreboardObjective(objective)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
player.sendPacket(buildObjective(0, objectiveName, title, display));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
player.sendPacket(buildObjective(1, objectiveName, "", HealthDisplay.INTEGER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
player.sendPacket(buildObjective(2, objectiveName, title, display));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object buildObjective(int action, String objectiveName, String title, @NotNull HealthDisplay display) {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
return newObjectivePacket.newInstance(
|
||||
newScoreboardObjective.newInstance(null, objectiveName, null,
|
||||
toComponent(IChatBaseComponent.optimizedComponent(title)),
|
||||
Enum.valueOf(EnumScoreboardHealthDisplay, display.name())),
|
||||
action
|
||||
);
|
||||
}
|
||||
Object nmsPacket = newObjectivePacket.newInstance();
|
||||
Objective_OBJECTIVE_NAME.set(nmsPacket, objectiveName);
|
||||
Objective_DISPLAY_NAME.set(nmsPacket, title);
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
Objective_RENDER_TYPE.set(nmsPacket, Enum.valueOf(EnumScoreboardHealthDisplay, display.name()));
|
||||
}
|
||||
Objective_METHOD.set(nmsPacket, action);
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
|
||||
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, true));
|
||||
} else {
|
||||
player.sendPacket(newTeamPacket.newInstance(team, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
player.sendPacket(TeamPacketConstructor_of.invoke(null, team));
|
||||
} else {
|
||||
player.sendPacket(newTeamPacket.newInstance(team, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
|
||||
} else {
|
||||
player.sendPacket(newTeamPacket.newInstance(team, 2));
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createTeam(@NotNull String teamName, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
|
||||
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
ScoreboardTeam_setPrefix.invoke(team, toComponent(IChatBaseComponent.optimizedComponent(prefix)));
|
||||
ScoreboardTeam_setSuffix.invoke(team, toComponent(IChatBaseComponent.optimizedComponent(suffix)));
|
||||
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(EnumChatFormatClass, EnumChatFormat.lastColorsOf(prefix).toString()));
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix.invoke(team, prefix);
|
||||
ScoreboardTeam_setSuffix.invoke(team, suffix);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
|
||||
if (BukkitReflection.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
|
||||
return team;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score));
|
||||
} else {
|
||||
Object scoreboardScore = newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(objective), playerName);
|
||||
ScoreboardScore_setScore.invoke(scoreboardScore, score);
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
player.sendPacket(newScorePacket.newInstance(scoreboardScore));
|
||||
} else {
|
||||
player.sendPacket(newScorePacket.newInstance(scoreboardScore, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, playerName, 0));
|
||||
} else {
|
||||
player.sendPacket(newScorePacket_String.newInstance(playerName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Scoreboard Objective with given name.
|
||||
*
|
||||
* @param objectiveName
|
||||
* Objective name
|
||||
* @return NMS Objective
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object newScoreboardObjective(@NotNull String objectiveName) {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
return newScoreboardObjective.newInstance(
|
||||
null,
|
||||
objectiveName,
|
||||
null,
|
||||
toComponent(new IChatBaseComponent("")),
|
||||
null
|
||||
);
|
||||
}
|
||||
return newScoreboardObjective.newInstance(
|
||||
null,
|
||||
objectiveName,
|
||||
IScoreboardCriteria_self.get(null)
|
||||
);
|
||||
}
|
||||
|
||||
private Object toComponent(IChatBaseComponent component) {
|
||||
return player.getPlatform().toComponent(component, player.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.hook.AdventureHook;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.scoreboard.Objective;
|
||||
import org.bukkit.scoreboard.RenderType;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Scoreboard handler using Paper API, which got added
|
||||
* in early 1.16.5 builds. Unlike the Bukkit API, the new
|
||||
* methods using adventure components do not have any
|
||||
* pointless artificial limits added in.
|
||||
* <p>
|
||||
* However, it still inherits the issue with compatibility
|
||||
* with other plugins that create scoreboards.
|
||||
*/
|
||||
public class PaperScoreboard extends BukkitScoreboard {
|
||||
|
||||
/** Flag tracking whether this implementation is available for use */
|
||||
@Getter
|
||||
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
|
||||
ReflectionUtils.methodExists(Team.class, "prefix", Component.class);
|
||||
|
||||
public PaperScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
|
||||
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.valueOf(display.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
|
||||
objective.displayName(toAdventure(displayName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
|
||||
team.prefix(toAdventure(prefix));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
|
||||
team.suffix(toAdventure(suffix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts raw text into adventure component
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @return Converted component
|
||||
*/
|
||||
@NotNull
|
||||
private Component toAdventure(@NotNull String text) {
|
||||
return AdventureHook.toAdventureComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
main: me.neznamy.tab.platforms.bukkit.BukkitTAB
|
||||
name: @name@
|
||||
version: @version@
|
||||
description: @description@
|
||||
api-version: 1.13 # Defining to avoid legacy plugin warn
|
||||
load: POSTWORLD
|
||||
author: @author@
|
||||
website: "@website@"
|
||||
depend: []
|
||||
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate]
|
||||
loadbefore: []
|
||||
prefix: TAB
|
||||
libraries: []
|
||||
folia-supported: true
|
||||
commands:
|
||||
tab:
|
||||
description: Plugin's main command
|
||||
permissions:
|
||||
tab.admin:
|
||||
description: Grants all TAB permissions
|
||||
default: op
|
||||
tab.tabcomplete:
|
||||
description: Allows player to complete all TAB commands
|
||||
default: op
|
||||
tab.reload:
|
||||
description: Allows player to run /tab reload
|
||||
default: op
|
||||
tab.cpu:
|
||||
description: Allows player to run /tab cpu
|
||||
default: op
|
||||
tab.debug:
|
||||
description: Allows player to run /tab debug [player]
|
||||
default: op
|
||||
tab.groupinfo:
|
||||
description: Allows player to run /tab group <group>
|
||||
default: op
|
||||
tab.grouplist:
|
||||
description: Allows player to run /tab groups
|
||||
default: op
|
||||
tab.parse:
|
||||
description: Allows player to run /tab parse <player> <placeholder>
|
||||
default: op
|
||||
tab.setcollision:
|
||||
description: Allows player to run /tab setcollision <player> <true/false>
|
||||
default: op
|
||||
tab.remove:
|
||||
description: Allows player to remove group/user using commands
|
||||
default: op
|
||||
tab.change.*:
|
||||
description: Allows player to change property using command
|
||||
default: op
|
||||
tab.width:
|
||||
description: Allows player to run /tab width
|
||||
default: op
|
||||
tab.announce.bar:
|
||||
description: Allows player to run /tab announce bar
|
||||
default: op
|
||||
tab.bossbar.toggle:
|
||||
description: Allows player to run /tab bossbar
|
||||
default: op
|
||||
tab.send.bar:
|
||||
description: Allows player to run /tab bossbar send
|
||||
default: op
|
||||
tab.mysql.download:
|
||||
description: Allows player to run /tab mysql download
|
||||
default: op
|
||||
tab.mysql.upload:
|
||||
description: Allows player to run /tab mysql upload
|
||||
default: op
|
||||
tab.nametag.toggle:
|
||||
description: Allows player to run /tab nametag toggle
|
||||
default: op
|
||||
tab.nametag.toggle.other:
|
||||
description: Allows player to run /tab nametag toggle [other player]
|
||||
default: op
|
||||
tab.nametag.preview:
|
||||
description: Allows player to run /tab nametag preview
|
||||
default: op
|
||||
tab.nametag.preview.other:
|
||||
description: Allows player to run /tab nametag preview [other player]
|
||||
default: op
|
||||
tab.announce.scoreboard:
|
||||
description: Allows player to run /tab scoreboard announce
|
||||
default: op
|
||||
tab.scoreboard.toggle:
|
||||
description: Allows player to run /tab scoreboard
|
||||
default: op
|
||||
tab.scoreboard.toggle.other:
|
||||
description: Allows player to run /tab scoreboard for other players
|
||||
default: op
|
||||
tab.scoreboard.show:
|
||||
description: Allows player to run /tab scoreboard show
|
||||
default: op
|
||||
tab.scoreboard.show.other:
|
||||
description: Allows player to run /tab scoreboard show for other players
|
||||
default: op
|
||||
tab.staff:
|
||||
description: Makes player counted in %staffonline% placeholder
|
||||
default: op
|
||||
tab.group.*:
|
||||
description: Considers player in group if assigning by permissions
|
||||
default: op
|
||||
tab.bypass:
|
||||
description: Makes player bypass per world playerlist if bypass permission is enabled
|
||||
default: op
|
||||
tab.seevanished:
|
||||
description: Marks player as able to see vanished players for features hooking into vanish plugins
|
||||
default: op
|
||||
tab.spectatorbypass:
|
||||
description: Makes player bypass spectator fix
|
||||
default: op
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bungeecord:3.0.1")
|
||||
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.1-SNAPSHOT")
|
||||
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
|
||||
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* BossBar handler for BungeeCord. It uses packets, since
|
||||
* BungeeCord does not have a BossBar API. Only supports
|
||||
* 1.9+ players, as dealing with entities would be simply impossible.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeBossBar implements BossBar {
|
||||
|
||||
@NotNull
|
||||
private final BungeeTabPlayer player;
|
||||
|
||||
/** Both are included in the same action, need to remember and write them both */
|
||||
@NotNull
|
||||
private final Map<UUID, Integer> colors = new HashMap<>();
|
||||
|
||||
@NotNull
|
||||
private final Map<UUID, Integer> styles = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 0);
|
||||
packet.setHealth(progress);
|
||||
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
|
||||
packet.setColor(color.ordinal());
|
||||
packet.setDivision(style.ordinal());
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
|
||||
colors.put(id, color.ordinal());
|
||||
styles.put(id, style.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 3);
|
||||
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 2);
|
||||
packet.setHealth(progress);
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
|
||||
packet.setDivision(style.ordinal());
|
||||
packet.setColor(colors.get(id));
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
|
||||
styles.put(id, style.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
|
||||
packet.setDivision(styles.get(id));
|
||||
packet.setColor(color.ordinal());
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
|
||||
colors.put(id, color.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
player.getPlayer().unsafe().sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
|
||||
|
||||
colors.remove(id);
|
||||
styles.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.ChatEvent;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerSwitchEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The core for BungeeCord forwarding events into all enabled features
|
||||
*/
|
||||
public class BungeeEventListener extends EventListener<ProxiedPlayer> implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerDisconnectEvent e) {
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onSwitch(ServerSwitchEvent e) {
|
||||
serverChange(e.getPlayer(), e.getPlayer().getUniqueId(), e.getPlayer().getServer().getInfo().getName());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onCommand(ChatEvent e) {
|
||||
if (e.isCommand() && command(((ProxiedPlayer)e.getSender()).getUniqueId(), e.getMessage())) {
|
||||
e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(PluginMessageEvent e) {
|
||||
if (!e.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
|
||||
if (e.getReceiver() instanceof ProxiedPlayer) {
|
||||
e.setCancelled(true);
|
||||
pluginMessage(((ProxiedPlayer) e.getReceiver()).getUniqueId(), e.getData());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabPlayer createPlayer(@NotNull ProxiedPlayer player) {
|
||||
return new BungeeTabPlayer((BungeePlatform) TAB.getInstance().getPlatform(), player);
|
||||
}
|
||||
}
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.features.redis.feature.RedisTeams;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.redis.RedisPlayer;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.Protocol;
|
||||
import net.md_5.bungee.protocol.packet.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Pipeline injection to secure proper functionality
|
||||
* of some features by preventing other plugins
|
||||
* from overriding it.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/** Inaccessible bungee internals */
|
||||
@Nullable
|
||||
private static Field wrapperField;
|
||||
|
||||
@Nullable
|
||||
private static Object directionData;
|
||||
|
||||
@Nullable
|
||||
private static Method getId;
|
||||
|
||||
static {
|
||||
try {
|
||||
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
|
||||
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
|
||||
getId = ReflectionUtils.setAccessible(directionData.getClass().getDeclaredMethod("getId", Class.class, int.class));
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/** Packets used by the plugin that must be deserialized and BungeeCord does not do it automatically */
|
||||
@NotNull
|
||||
private final Class<? extends DefinedPacket>[] extraPacketClasses = new Class[]{Team.class, ScoreboardDisplay.class, ScoreboardObjective.class};
|
||||
|
||||
@NotNull
|
||||
private final Supplier<DefinedPacket>[] extraPacketSuppliers = new Supplier[]{Team::new, ScoreboardDisplay::new, ScoreboardObjective::new};
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
|
||||
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : TabChannelDuplexHandler::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
if (wrapperField == null) return null;
|
||||
final BungeeTabPlayer bungee = (BungeeTabPlayer) player;
|
||||
try {
|
||||
return ((ChannelWrapper) wrapperField.get(bungee.getPlayer().getPendingConnection())).getHandle();
|
||||
} catch (IllegalAccessException exception) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(
|
||||
player,
|
||||
((Number) ScoreboardDisplay.class.getMethod("getPosition").invoke(packet)).intValue(),
|
||||
((ScoreboardDisplay) packet).getName()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
((ScoreboardObjective) packet).getAction(), ((ScoreboardObjective) packet).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayObjective(@NotNull Object packet) {
|
||||
return packet instanceof ScoreboardDisplay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObjective(@NotNull Object packet) {
|
||||
return packet instanceof ScoreboardObjective;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeam(@NotNull Object packet) {
|
||||
return packet instanceof Team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(@NotNull Object packet) {
|
||||
return packet instanceof PlayerListItem || packet instanceof PlayerListItemUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyPlayers(@NotNull Object team) {
|
||||
if (TAB.getInstance().getNameTagManager() == null) return;
|
||||
Team packet = (Team) team;
|
||||
if (packet.getMode() == 1 || packet.getMode() == 2 || packet.getMode() == 4) return;
|
||||
Collection<String> col = Lists.newArrayList(packet.getPlayers());
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
String expectedTeam = sorting.getShortTeamName(p);
|
||||
if (expectedTeam != null && (col.contains(p.getNickname()) || col.contains(p.getName())) &&
|
||||
!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !packet.getName().equals(expectedTeam)) {
|
||||
logTeamOverride(packet.getName(), p.getName(), expectedTeam);
|
||||
col.remove(p.getNickname());
|
||||
col.remove(p.getName());
|
||||
}
|
||||
}
|
||||
RedisSupport redis = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
|
||||
if (redis != null) {
|
||||
RedisTeams teams = redis.getRedisTeams();
|
||||
if (teams != null) {
|
||||
for (RedisPlayer p : redis.getRedisPlayers().values()) {
|
||||
if (col.contains(p.getNickname()) && !packet.getName().equals(teams.getTeamNames().get(p))) {
|
||||
logTeamOverride(packet.getName(), p.getNickname(), teams.getTeamNames().get(p));
|
||||
col.remove(p.getNickname());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
packet.setPlayers(col.toArray(new String[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
if (packet instanceof PlayerListItemUpdate) {
|
||||
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
|
||||
for (PlayerListItem.Item item : update.getItems()) {
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
|
||||
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
|
||||
}
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
|
||||
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
|
||||
}
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PlayerListItem listItem = (PlayerListItem) packet;
|
||||
for (PlayerListItem.Item item : listItem.getItems()) {
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
|
||||
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
|
||||
}
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
|
||||
}
|
||||
if (listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance of the feature
|
||||
*/
|
||||
public BungeePipelineInjector() {
|
||||
super("inbound-boss");
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel duplex handler override if features using packets that must be
|
||||
* deserialized manually are used. If they are disabled, deserialization is
|
||||
* disabled for better performance.
|
||||
*/
|
||||
public class DeserializableBungeeChannelDuplexHandler extends TabChannelDuplexHandler {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player
|
||||
*
|
||||
* @param player
|
||||
* player to inject
|
||||
*/
|
||||
public DeserializableBungeeChannelDuplexHandler(@NotNull TabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull ChannelHandlerContext context, @NotNull Object packet, @NotNull ChannelPromise channelPromise) {
|
||||
long time = System.nanoTime();
|
||||
Object modifiedPacket = packet instanceof ByteBuf ? deserialize((ByteBuf) packet) : packet;
|
||||
TAB.getInstance().getCPUManager().addTime(TabConstants.Feature.PACKET_DESERIALIZING, TabConstants.CpuUsageCategory.BYTE_BUF, System.nanoTime()-time);
|
||||
super.write(context, modifiedPacket, channelPromise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes byte buf in case it is one of the tracked packets coming from backend server and returns it.
|
||||
* If the packet is not one of them, returns input
|
||||
*
|
||||
* @param buf
|
||||
* byte buf to deserialize
|
||||
* @return deserialized packet or input byte buf if packet is not tracked
|
||||
*/
|
||||
@NotNull
|
||||
private Object deserialize(@NotNull ByteBuf buf) {
|
||||
int marker = buf.readerIndex();
|
||||
try {
|
||||
int packetId = buf.readByte();
|
||||
for (int i=0; i<extraPacketClasses.length; i++) {
|
||||
if (packetId == getPacketId(((BungeeTabPlayer)player).getPlayer().getPendingConnection().getVersion(), extraPacketClasses[i])) {
|
||||
DefinedPacket packet = extraPacketSuppliers[i].get();
|
||||
packet.read(buf, null, ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion());
|
||||
buf.release();
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//rare OverflowPacketException or IndexOutOfBoundsException
|
||||
}
|
||||
buf.readerIndex(marker);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns packet ID of specified packet on the protocol version
|
||||
*
|
||||
* @param protocolVersion
|
||||
* Protocol version to get packet id for
|
||||
* @param clazz
|
||||
* packet class
|
||||
* @return packet ID
|
||||
*/
|
||||
private int getPacketId(int protocolVersion, @NotNull Class<? extends DefinedPacket> clazz) {
|
||||
if (getId == null) return -1;
|
||||
try {
|
||||
return (int) getId.invoke(directionData, clazz, protocolVersion);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
|
||||
" with client version " + protocolVersion, e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bungeecord.features.BungeeRedisSupport;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.hook.ViaVersionHook;
|
||||
import me.neznamy.tab.shared.proxy.ProxyPlatform;
|
||||
import me.neznamy.tab.shared.util.ComponentCache;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* BungeeCord implementation of Platform
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class BungeePlatform extends ProxyPlatform<BaseComponent> {
|
||||
|
||||
@NotNull
|
||||
private final BungeeTAB plugin;
|
||||
|
||||
/** Component cache for BungeeCord components */
|
||||
@NotNull
|
||||
private final ComponentCache<IChatBaseComponent, BaseComponent> bungeeCache =
|
||||
new ComponentCache<>(1000, this::toComponent0);
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
ViaVersionHook.getInstance().printProxyWarn();
|
||||
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||
TAB.getInstance().addPlayer(new BungeeTabPlayer(this, p));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public RedisSupport getRedisSupport() {
|
||||
if (ReflectionUtils.classExists("com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI") &&
|
||||
RedisBungeeAPI.getRedisBungeeApi() != null) {
|
||||
return new BungeeRedisSupport(plugin);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull IChatBaseComponent message) {
|
||||
plugin.getLogger().info(message.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull IChatBaseComponent message) {
|
||||
plugin.getLogger().warning(EnumChatFormat.RED.getFormat() + message.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[BungeeCord] " + plugin.getProxy().getName() + " - " + plugin.getProxy().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener() {
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(plugin, new BungeeEventListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommand() {
|
||||
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new BungeeTabCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
new Metrics(plugin, 10535).addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED,
|
||||
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
|
||||
return bungeeCache.get(component, version);
|
||||
}
|
||||
|
||||
private BaseComponent toComponent0(IChatBaseComponent component, ProtocolVersion version) {
|
||||
ChatModifier modifier = component.getModifier();
|
||||
TextComponent textComponent = new TextComponent(component.getText());
|
||||
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
|
||||
modifier.getColor().toString(version.getMinorVersion() >= 16)));
|
||||
if (modifier.isBold()) textComponent.setBold(true);
|
||||
if (modifier.isItalic()) textComponent.setItalic(true);
|
||||
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
|
||||
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
|
||||
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
|
||||
if (modifier.getFont() != null) textComponent.setFont(modifier.getFont());
|
||||
if (!component.getExtra().isEmpty()) textComponent.setExtra(
|
||||
component.getExtra().stream().map(c -> toComponent0(c, version)).collect(Collectors.toList()));
|
||||
return textComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return new BungeePipelineInjector();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerChannel() {
|
||||
ProxyServer.getInstance().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardScore;
|
||||
import net.md_5.bungee.protocol.packet.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Scoreboard handler for BungeeCord. Because it does not offer
|
||||
* any Scoreboard API and the scoreboard class it has is just a
|
||||
* downstream tracker, we need to use packets.
|
||||
*/
|
||||
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
|
||||
/** Constructor to support both <1.20.2 and 1.20.2+ builds */
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Constructor<ScoreboardDisplay> newScoreboardDisplay = (Constructor<ScoreboardDisplay>)
|
||||
Arrays.stream(ScoreboardDisplay.class.getConstructors()).filter(c -> c.getParameterCount() == 2).findAny().orElse(null);
|
||||
|
||||
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
player.getPlayer().unsafe().sendPacket(newScoreboardDisplay.newInstance((byte)slot.ordinal(), objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
|
||||
objectiveName,
|
||||
jsonOrRaw(title),
|
||||
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
|
||||
(byte) 0
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
|
||||
objectiveName,
|
||||
"", // Empty string to prevent kick on 1.7
|
||||
null,
|
||||
(byte) 1
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
|
||||
objectiveName,
|
||||
jsonOrRaw(title),
|
||||
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
|
||||
(byte) 2
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
int color = 0;
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
|
||||
}
|
||||
player.getPlayer().unsafe().sendPacket(new Team(name, (byte) 0, jsonOrRaw(name), jsonOrRaw(prefix), jsonOrRaw(suffix),
|
||||
visibility.toString(), collision.toString(), color, (byte)options, players.toArray(new String[0])));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
player.getPlayer().unsafe().sendPacket(new Team(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
int color = 0;
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
|
||||
}
|
||||
player.getPlayer().unsafe().sendPacket(new Team(name, (byte) 2, jsonOrRaw(name), jsonOrRaw(prefix),
|
||||
jsonOrRaw(suffix), visibility.toString(), collision.toString(), color, (byte)options, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
player.getPlayer().unsafe().sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
player.getPlayer().unsafe().sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* If player's version is 1.13+, creates a component from given text and returns
|
||||
* it as a serialized component, which BungeeCord uses.
|
||||
* <p>
|
||||
* If player's version is 1.12-, the text is returned
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @return serialized component for 1.13+ clients, cut string for 1.12-
|
||||
*/
|
||||
@NotNull
|
||||
private String jsonOrRaw(@NotNull String text) {
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toString(player.getVersion());
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Main class for BungeeCord.
|
||||
*/
|
||||
public class BungeeTAB extends Plugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
|
||||
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 " +
|
||||
"(with 1.19.3 support) and up (or an equivalent fork) to work."));
|
||||
return;
|
||||
}
|
||||
TAB.create(new BungeePlatform(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.TabExecutor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class BungeeTabCommand extends Command implements TabExecutor {
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BungeeTabCommand() {
|
||||
super(TabConstants.COMMAND_PROXY, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull CommandSender sender, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
|
||||
sender.sendMessage(new TextComponent(EnumChatFormat.color(message)));
|
||||
}
|
||||
} else {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof ProxiedPlayer) {
|
||||
p = TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId());
|
||||
if (p == null) return; //player not loaded correctly
|
||||
}
|
||||
TAB.getInstance().getCommand().execute(p, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Iterable<String> onTabComplete(@NotNull CommandSender sender, @NotNull String[] args) {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof ProxiedPlayer) {
|
||||
p = TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId());
|
||||
if (p == null) return Collections.emptyList(); //player not loaded correctly
|
||||
}
|
||||
return TAB.getInstance().getCommand().complete(p, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1193;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList17;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList18;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for BungeeCord
|
||||
*/
|
||||
@Getter
|
||||
public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
/** Flag tracking plugin presence */
|
||||
private static final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
|
||||
|
||||
/** Player's scoreboard */
|
||||
@NotNull
|
||||
private final Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
|
||||
|
||||
/** Player's tab list based on version */
|
||||
@NotNull
|
||||
private final TabList tabList1_7 = new BungeeTabList17(this);
|
||||
|
||||
@NotNull
|
||||
private final TabList tabList1_8 = new BungeeTabList18(this);
|
||||
|
||||
@NotNull
|
||||
private final TabList tabList1_19_3 = new BungeeTabList1193(this);
|
||||
|
||||
/** Player's boss bar view */
|
||||
@NotNull
|
||||
private final BossBar bossBar = new BungeeBossBar(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance for given player
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param p
|
||||
* BungeeCord player
|
||||
*/
|
||||
public BungeeTabPlayer(@NotNull BungeePlatform platform, @NotNull ProxiedPlayer p) {
|
||||
super(platform, p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission0(@NotNull String permission) {
|
||||
return getPlayer().hasPermission(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
return getPlayer().getPing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
getPlayer().sendMessage(getPlatform().toComponent(message, getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TabList.Skin getSkin() {
|
||||
LoginResult loginResult = ((InitialHandler)getPlayer().getPendingConnection()).getLoginProfile();
|
||||
if (loginResult == null) return null;
|
||||
Property[] properties = loginResult.getProperties();
|
||||
if (properties == null || properties.length == 0) return null; //Offline mode
|
||||
return new TabList.Skin(properties[0].getValue(), properties[0].getSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ProxiedPlayer getPlayer() {
|
||||
return (ProxiedPlayer) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer().isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BungeePlatform getPlatform() {
|
||||
return (BungeePlatform) platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginMessage(byte[] message) {
|
||||
if (getPlayer().getServer() == null) {
|
||||
errorNoServer(message);
|
||||
return;
|
||||
}
|
||||
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* If ViaVersion is installed on BungeeCord, it changes protocol to match version
|
||||
* of server to which player is connected to. For that reason, we need to retrieve
|
||||
* the field more often than just on join.
|
||||
*
|
||||
* @return Player's current protocol version
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public ProtocolVersion getVersion() {
|
||||
return ProtocolVersion.fromNetworkId(getPlayer().getPendingConnection().getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
|
||||
} catch (IllegalStateException ignored) {
|
||||
// PV Bug: PremiumVanish must be enabled to use its API
|
||||
}
|
||||
return super.isVanished();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList getTabList() {
|
||||
int version = getPlayer().getPendingConnection().getVersion();
|
||||
if (version >= ProtocolVersion.V1_19_3.getNetworkId()) return tabList1_19_3;
|
||||
if (version >= ProtocolVersion.V1_8.getNetworkId()) return tabList1_8;
|
||||
return tabList1_7;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.features;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* RedisBungee implementation for BungeeCord
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class BungeeRedisSupport extends RedisSupport implements Listener {
|
||||
|
||||
/** Plugin reference for registering listener */
|
||||
@NotNull
|
||||
private final BungeeTAB plugin;
|
||||
|
||||
@EventHandler
|
||||
public void onMessage(@NotNull PubSubMessageEvent e) {
|
||||
if (!e.getChannel().equals(TabConstants.REDIS_CHANNEL_NAME)) return;
|
||||
processMessage(e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register() {
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(plugin, this);
|
||||
RedisBungeeAPI.getRedisBungeeApi().registerPubSubChannels(TabConstants.REDIS_CHANNEL_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
ProxyServer.getInstance().getPluginManager().unregisterListener(this);
|
||||
RedisBungeeAPI.getRedisBungeeApi().unregisterPubSubChannels(TabConstants.REDIS_CHANNEL_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull String message) {
|
||||
RedisBungeeAPI.getRedisBungeeApi().sendChannelMessage(TabConstants.REDIS_CHANNEL_NAME, message);
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItemRemove;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.19.3+ players using the new tab list packets.
|
||||
* Because BungeeCord does not have a TabList API, we need to use packets.
|
||||
* They are sent using an internal BungeeCord method that keeps track of them,
|
||||
* so they are removed on server switch to secure parity with Velocity.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeTabList1193 implements TabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
@NotNull
|
||||
private final BungeeTabPlayer player;
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
PlayerListItemRemove remove = new PlayerListItemRemove();
|
||||
remove.setUuids(new UUID[]{entry});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(remove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
Item item = item(entry);
|
||||
item.setPing(latency);
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY), item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
Item item = item(entry);
|
||||
item.setGamemode(gameMode);
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE), item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
Item item = item(entry.getUniqueId());
|
||||
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
|
||||
item.setGamemode(entry.getGameMode());
|
||||
item.setListed(true);
|
||||
item.setPing(entry.getLatency());
|
||||
if (entry.getSkin() != null) {
|
||||
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
|
||||
} else {
|
||||
item.setProperties(new Property[0]);
|
||||
}
|
||||
item.setUsername(entry.getName());
|
||||
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), item);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Item item(@NotNull UUID id) {
|
||||
Item item = new Item();
|
||||
item.setUuid(id);
|
||||
return item;
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull EnumSet<PlayerListItemUpdate.Action> actions, @NotNull Item item) {
|
||||
PlayerListItemUpdate packet = new PlayerListItemUpdate();
|
||||
packet.setActions(actions);
|
||||
packet.setItems(new Item[]{item});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.getPlayer().setTabHeader(
|
||||
player.getPlatform().toComponent(header, player.getVersion()),
|
||||
player.getPlatform().toComponent(footer, player.getVersion())
|
||||
);
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.7 players, which has a completely different
|
||||
* system. Entries are identified by display name, not uuids. Display
|
||||
* names are changed by removing old entry and adding a new one, which
|
||||
* requires further tracking than just the UUID.<p>
|
||||
* Because BungeeCord does not have a TabList API, we need to use packets.
|
||||
* They are sent using an internal BungeeCord method that keeps track of them,
|
||||
* so they are removed on server switch to secure parity with Velocity.
|
||||
* While BungeeCord itself does not support 1.7, some of its forks do.
|
||||
* This was tested on FlameCord fork.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeTabList17 implements TabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
@NotNull
|
||||
private final BungeeTabPlayer player;
|
||||
|
||||
/** Because entries are identified by names and not uuids on 1.7 */
|
||||
@NotNull
|
||||
private final Map<UUID, String> userNames = new HashMap<>();
|
||||
|
||||
@NotNull
|
||||
private final Map<UUID, String> displayNames = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
|
||||
|
||||
// Remove from map
|
||||
userNames.remove(entry);
|
||||
displayNames.remove(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
|
||||
addEntry(new Entry.Builder(entry).displayName(displayName).name(userNames.get(entry)).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.UPDATE_LATENCY, createItem(null, displayNames.get(entry), latency));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {/*Added in 1.8*/}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
String displayNameString = entry.getDisplayName() == null ? String.valueOf(entry.getName()) : entry.getDisplayName().toLegacyText();
|
||||
if (displayNameString.length() > 16) displayNameString = displayNameString.substring(0, 16); // 16 character limit
|
||||
update(PlayerListItem.Action.ADD_PLAYER, createItem(entry.getName(), displayNameString, entry.getLatency()));
|
||||
|
||||
// Add to map
|
||||
userNames.put(entry.getUniqueId(), entry.getName());
|
||||
displayNames.put(entry.getUniqueId(), displayNameString);
|
||||
}
|
||||
|
||||
private void update(@NotNull PlayerListItem.Action action, @NotNull PlayerListItem.Item item) {
|
||||
PlayerListItem packet = new PlayerListItem();
|
||||
packet.setAction(action);
|
||||
packet.setItems(new PlayerListItem.Item[]{item});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
|
||||
}
|
||||
|
||||
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
|
||||
PlayerListItem.Item item = new PlayerListItem.Item();
|
||||
item.setUsername(username);
|
||||
item.setDisplayName(displayName);
|
||||
item.setPing(latency);
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
// Added in 1.8
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler for players between 1.8 and 1.19.2.<p>
|
||||
* Because BungeeCord does not have a TabList API, we need to use packets.
|
||||
* They are sent using an internal BungeeCord method that keeps track of them,
|
||||
* so they are removed on server switch to secure parity with Velocity.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeTabList18 implements TabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
@NotNull
|
||||
private final BungeeTabPlayer player;
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
sendPacket(PlayerListItem.Action.REMOVE_PLAYER, item(entry));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
|
||||
sendPacket(PlayerListItem.Action.UPDATE_DISPLAY_NAME, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
Item item = item(entry);
|
||||
item.setPing(latency);
|
||||
sendPacket(PlayerListItem.Action.UPDATE_LATENCY, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
Item item = item(entry);
|
||||
item.setGamemode(gameMode);
|
||||
sendPacket(PlayerListItem.Action.UPDATE_GAMEMODE, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
Item item = item(entry.getUniqueId());
|
||||
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
|
||||
item.setGamemode(entry.getGameMode());
|
||||
item.setPing(entry.getLatency());
|
||||
if (entry.getSkin() != null) {
|
||||
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
|
||||
} else {
|
||||
item.setProperties(new Property[0]);
|
||||
}
|
||||
item.setUsername(entry.getName());
|
||||
sendPacket(PlayerListItem.Action.ADD_PLAYER, item);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Item item(@NotNull UUID id) {
|
||||
Item item = new Item();
|
||||
item.setUuid(id);
|
||||
return item;
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull PlayerListItem.Action action, @NotNull Item item) {
|
||||
PlayerListItem packet = new PlayerListItem();
|
||||
packet.setAction(action);
|
||||
packet.setItems(new Item[]{item});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.getPlayer().setTabHeader(
|
||||
player.getPlatform().toComponent(header, player.getVersion()),
|
||||
player.getPlatform().toComponent(footer, player.getVersion())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
name: @name@
|
||||
main: me.neznamy.tab.platforms.bungeecord.BungeeTAB
|
||||
version: @version@
|
||||
author: @author@
|
||||
description: @description@
|
||||
depends: []
|
||||
softDepends: [RedisBungee, LuckPerms, PremiumVanish]
|
||||
@@ -0,0 +1,77 @@
|
||||
val minecraftVersion = "1.20.2"
|
||||
|
||||
val v114 = "0.28.5+1.14"
|
||||
val v115 = "0.28.5+1.15"
|
||||
val v116 = "0.42.0+1.16"
|
||||
|
||||
val fabricApiVersions = mapOf(
|
||||
"1.20.2" to "0.88.5+1.20.2",
|
||||
"1.20.1" to "0.83.0+1.20.1",
|
||||
"1.20" to "0.83.0+1.20",
|
||||
"1.19.4" to "0.77.0+1.19.4",
|
||||
"1.19.3" to "0.76.0+1.19.3",
|
||||
"1.19.2" to "0.76.0+1.19.2",
|
||||
"1.19.1" to "0.58.5+1.19.1",
|
||||
"1.19" to "0.58.0+1.19",
|
||||
"1.18.2" to "0.76.0+1.18.2",
|
||||
"1.18.1" to "0.46.6+1.18",
|
||||
"1.18" to "0.46.6+1.18",
|
||||
"1.17.1" to "0.46.1+1.17",
|
||||
"1.17" to "0.46.1+1.17",
|
||||
"1.16.5" to v116,
|
||||
"1.16.4" to v116,
|
||||
"1.16.3" to v116,
|
||||
"1.16.2" to v116,
|
||||
"1.16.1" to v116,
|
||||
"1.16" to v116,
|
||||
"1.15.2" to v115,
|
||||
"1.15.1" to v115,
|
||||
"1.15" to v115,
|
||||
"1.14.4" to v114,
|
||||
"1.14.3" to v114,
|
||||
"1.14.2" to v114,
|
||||
"1.14.1" to v114,
|
||||
"1.14" to v114,
|
||||
)
|
||||
|
||||
plugins {
|
||||
id("fabric-loom")
|
||||
}
|
||||
|
||||
repositories {
|
||||
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
|
||||
maven("https://jitpack.io") // YamlAssist
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
|
||||
minecraft("com.mojang:minecraft:$minecraftVersion")
|
||||
mappings(loom.officialMojangMappings())
|
||||
|
||||
modImplementation("net.fabricmc", "fabric-loader", "0.14.17")
|
||||
modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT")
|
||||
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersions[minecraftVersion]))
|
||||
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersions[minecraftVersion]))
|
||||
modImplementation(fabricApi.module("fabric-networking-api-v1", fabricApiVersions[minecraftVersion]))
|
||||
modImplementation(fabricApi.module("fabric-entity-events-v1", fabricApiVersions[minecraftVersion]))
|
||||
modImplementation(fabricApi.module("fabric-command-api-v${if (minecraftVersion.split(".")[1].toInt() >= 19) "2" else "1"}", fabricApiVersions[minecraftVersion]))
|
||||
}
|
||||
|
||||
loom {
|
||||
accessWidenerPath.set(file("src/main/resources/tab.accesswidener"))
|
||||
}
|
||||
|
||||
tasks {
|
||||
compileJava {
|
||||
options.release.set(17)
|
||||
}
|
||||
validateAccessWidener {
|
||||
// We don't want to validate the access wideners, as we have wideners for multiple versions, and the validation task will
|
||||
// fail, as the classes for the old wideners don't exist in the new versions and vice versa.
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import net.minecraft.server.level.ServerBossEvent;
|
||||
import net.minecraft.world.BossEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class FabricBossBar implements BossBar {
|
||||
|
||||
@NotNull
|
||||
private final FabricTabPlayer player;
|
||||
|
||||
@NotNull
|
||||
private final Map<UUID, ServerBossEvent> bars = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
ServerBossEvent bar = new ServerBossEvent(
|
||||
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
|
||||
BossEvent.BossBarColor.valueOf(color.name()),
|
||||
BossEvent.BossBarOverlay.valueOf(style.name())
|
||||
);
|
||||
bar.setProgress(progress);
|
||||
bars.put(id, bar);
|
||||
bar.addPlayer(player.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
bars.get(id).setName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
bars.get(id).setProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
bars.get(id).setOverlay(BossEvent.BossBarOverlay.valueOf(style.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
bars.get(id).setColor(BossEvent.BossBarColor.valueOf(color.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
bars.remove(id).removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.backend.entityview.EntityView;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record FabricEntityView(FabricTabPlayer player) implements EntityView {
|
||||
|
||||
@NotNull
|
||||
private static final Entity dummyEntity = new ArmorStand(null, 0, 0, 0);
|
||||
|
||||
@Override
|
||||
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location,
|
||||
@NotNull EntityData data) {
|
||||
player.sendPacket(new ClientboundAddEntityPacket(entityId, id,
|
||||
location.getX(), location.getY(), location.getZ(), 0, 0,
|
||||
(EntityType<?>) entityType, 0, Vec3.ZERO, 0));
|
||||
updateEntityMetadata(entityId, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
player.sendPacket(new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build()));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleportEntity(int entityId, @NotNull Location location) {
|
||||
dummyEntity.setId(entityId);
|
||||
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
|
||||
player.sendPacket(new ClientboundTeleportEntityPacket(dummyEntity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyEntities(int... entities) {
|
||||
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyPacket(@NotNull Object packet) {
|
||||
return packet instanceof ClientboundRemoveEntitiesPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeleportPacket(@NotNull Object packet) {
|
||||
return packet instanceof ClientboundTeleportEntityPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
|
||||
return packet instanceof ClientboundAddEntityPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMovePacket(@NotNull Object packet) {
|
||||
return packet instanceof ClientboundMoveEntityPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLookPacket(@NotNull Object packet) {
|
||||
return packet instanceof ClientboundMoveEntityPacket.Rot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTeleportEntityId(@NotNull Object teleportPacket) {
|
||||
return ((ClientboundTeleportEntityPacket) teleportPacket).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMoveEntityId(@NotNull Object movePacket) {
|
||||
return ((ClientboundMoveEntityPacket) movePacket).entityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
|
||||
return ((ClientboundAddEntityPacket) playerSpawnPacket).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
|
||||
return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds().toIntArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FabricEventListener extends EventListener<ServerPlayer> {
|
||||
|
||||
public void register() {
|
||||
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
|
||||
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
|
||||
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register((player, origin, destination) ->
|
||||
worldChange(player.getUUID(), destination.dimension().location().toString()));
|
||||
//TODO command
|
||||
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> replacePlayer(oldPlayer.getUUID(), newPlayer));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabPlayer createPlayer(@NotNull ServerPlayer player) {
|
||||
return new FabricTabPlayer((FabricPlatform) TAB.getInstance().getPlatform(), player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Action;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Entry;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
public class FabricPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
public FabricPipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return ((FabricTabPlayer)player).getPlayer().connection.connection.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
|
||||
((ClientboundSetDisplayObjectivePacket)packet).getSlot().ordinal(),
|
||||
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
((ClientboundSetObjectivePacket)packet).getMethod(),
|
||||
((ClientboundSetObjectivePacket)packet).getObjectiveName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayObjective(@NotNull Object packet) {
|
||||
return packet instanceof ClientboundSetDisplayObjectivePacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObjective(@NotNull Object packet) {
|
||||
return packet instanceof ClientboundSetObjectivePacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeam(@NotNull Object packet) {
|
||||
return packet instanceof ClientboundSetPlayerTeamPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(@NotNull Object packet) {
|
||||
return packet instanceof ClientboundPlayerInfoUpdatePacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
|
||||
EnumSet<Action> actions = packet.actions();
|
||||
List<Entry> updatedList = new ArrayList<>();
|
||||
for (Entry nmsData : packet.entries()) {
|
||||
GameProfile profile = nmsData.profile();
|
||||
Component displayName = nmsData.displayName();
|
||||
int latency = nmsData.latency();
|
||||
if (actions.contains(Action.UPDATE_DISPLAY_NAME)) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
|
||||
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
|
||||
}
|
||||
if (actions.contains(Action.UPDATE_LATENCY)) {
|
||||
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
|
||||
}
|
||||
if (actions.contains(Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
|
||||
}
|
||||
updatedList.add(new Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
|
||||
}
|
||||
packet.entries = updatedList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyPlayers(@NotNull Object teamPacket) {
|
||||
if (TAB.getInstance().getNameTagManager() == null) return;
|
||||
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
|
||||
int action = packet.method;
|
||||
if (action == 1 || action == 2 || action == 4) return;
|
||||
Collection<String> players = packet.getPlayers();
|
||||
String teamName = packet.getName();
|
||||
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;
|
||||
}
|
||||
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
String expectedTeam = sorting.getShortTeamName(p);
|
||||
if (expectedTeam == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
|
||||
logTeamOverride(teamName, p.getName(), expectedTeam);
|
||||
} else {
|
||||
newList.add(entry);
|
||||
}
|
||||
}
|
||||
packet.players = newList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.chat.contents.LiteralContents;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Platform implementation for Fabric
|
||||
*/
|
||||
public record FabricPlatform(MinecraftServer server) implements BackendPlatform<Component> {
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (ServerPlayer player : PlayerLookup.all(server)) {
|
||||
TAB.getInstance().addPlayer(new FabricTabPlayer(this, player));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return new FabricPipelineInjector();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public NameTag getUnlimitedNameTags() {
|
||||
return new FabricNameTagX();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabExpansion createTabExpansion() {
|
||||
return new EmptyTabExpansion();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TabFeature getPerWorldPlayerList() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull IChatBaseComponent message) {
|
||||
MinecraftServer.LOGGER.info("[TAB] " + message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull IChatBaseComponent message) {
|
||||
MinecraftServer.LOGGER.warn("[TAB] " + message.toRawText()); // Fabric console does not support colors
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener() {
|
||||
new FabricEventListener().register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommand() {
|
||||
// Event listener must be registered in main class
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
// Not available
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ProtocolVersion getServerVersion() {
|
||||
return ProtocolVersion.fromFriendlyName(SharedConstants.getCurrentVersion().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
|
||||
// Text
|
||||
MutableComponent comp = MutableComponent.create(new LiteralContents(component.getText()));
|
||||
|
||||
// Color and style
|
||||
List<ChatFormatting> formats = new ArrayList<>();
|
||||
if (component.getModifier().isUnderlined()) formats.add(ChatFormatting.UNDERLINE);
|
||||
if (component.getModifier().isObfuscated()) formats.add(ChatFormatting.OBFUSCATED);
|
||||
if (component.getModifier().isStrikethrough()) formats.add(ChatFormatting.STRIKETHROUGH);
|
||||
if (component.getModifier().isItalic()) formats.add(ChatFormatting.ITALIC);
|
||||
if (component.getModifier().isBold()) formats.add(ChatFormatting.BOLD);
|
||||
Style style = comp.getStyle().applyFormats(formats.toArray(new ChatFormatting[0]));
|
||||
if (component.getModifier().getColor() != null) {
|
||||
if (version.getMinorVersion() >= 16) {
|
||||
style = style.withColor(component.getModifier().getColor().getRgb());
|
||||
} else {
|
||||
style = style.withColor(ChatFormatting.valueOf(component.getModifier().getColor().getLegacyColor().name()));
|
||||
}
|
||||
}
|
||||
if (component.getModifier().getFont() != null)
|
||||
style = style.withFont(new ResourceLocation(component.getModifier().getFont()));
|
||||
comp.withStyle(style);
|
||||
|
||||
// Extra
|
||||
comp.getSiblings().addAll(component.getExtra().stream().map(c -> toComponent(c, version)).toList());
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getTPS() {
|
||||
return -1; // Not available
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMSPT() {
|
||||
return server.getAverageTickTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetScorePacket;
|
||||
import net.minecraft.server.ServerScoreboard;
|
||||
import net.minecraft.world.scores.Objective;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Team;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
|
||||
|
||||
@NotNull
|
||||
private static final net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
|
||||
|
||||
public FabricScoreboard(FabricTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
player.sendPacket(
|
||||
new ClientboundSetDisplayObjectivePacket(
|
||||
net.minecraft.world.scores.DisplaySlot.values()[slot.ordinal()],
|
||||
new Objective(
|
||||
dummyScoreboard,
|
||||
objective,
|
||||
ObjectiveCriteria.DUMMY,
|
||||
Component.empty(),
|
||||
ObjectiveCriteria.RenderType.INTEGER
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
player.sendPacket(
|
||||
new ClientboundSetObjectivePacket(
|
||||
new Objective(
|
||||
dummyScoreboard,
|
||||
objectiveName,
|
||||
ObjectiveCriteria.DUMMY,
|
||||
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
|
||||
ObjectiveCriteria.RenderType.valueOf(display.name())
|
||||
),
|
||||
0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
player.sendPacket(
|
||||
new ClientboundSetObjectivePacket(
|
||||
new Objective(
|
||||
dummyScoreboard,
|
||||
objectiveName,
|
||||
ObjectiveCriteria.DUMMY,
|
||||
Component.empty(),
|
||||
ObjectiveCriteria.RenderType.INTEGER
|
||||
),
|
||||
1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
player.sendPacket(
|
||||
new ClientboundSetObjectivePacket(
|
||||
new Objective(
|
||||
dummyScoreboard,
|
||||
objectiveName,
|
||||
ObjectiveCriteria.DUMMY,
|
||||
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
|
||||
ObjectiveCriteria.RenderType.valueOf(display.name())
|
||||
),
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
PlayerTeam team = new PlayerTeam(dummyScoreboard, name);
|
||||
team.setAllowFriendlyFire((options & 0x01) > 0);
|
||||
team.setSeeFriendlyInvisibles((options & 0x02) > 0);
|
||||
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
|
||||
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
|
||||
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
|
||||
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
|
||||
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
|
||||
team.getPlayers().addAll(players);
|
||||
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
player.sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket(new PlayerTeam(dummyScoreboard, name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
PlayerTeam team = new PlayerTeam(dummyScoreboard, name);
|
||||
team.setAllowFriendlyFire((options & 0x01) != 0);
|
||||
team.setSeeFriendlyInvisibles((options & 0x02) != 0);
|
||||
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
|
||||
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
|
||||
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
|
||||
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
|
||||
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
|
||||
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
player.sendPacket(new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, playerName, score));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
player.sendPacket(new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, playerName, 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import me.lucko.fabric.api.permissions.v0.Permissions;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import net.fabricmc.api.DedicatedServerModInitializer;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FabricTAB implements DedicatedServerModInitializer {
|
||||
|
||||
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
|
||||
|
||||
@Override
|
||||
public void onInitializeServer() {
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
|
||||
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
|
||||
}
|
||||
|
||||
public static boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
|
||||
if (source.hasPermission(4)) return true;
|
||||
return fabricPermissionsApi && Permissions.check(source, permission);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class FabricTabCommand {
|
||||
|
||||
public void onRegisterCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralCommandNode<CommandSourceStack> command = Commands.literal(TabConstants.COMMAND_BACKEND)
|
||||
.executes(context -> executeCommand(context.getSource(), new String[0]))
|
||||
.build();
|
||||
ArgumentCommandNode<CommandSourceStack, String> args = Commands.argument("args", StringArgumentType.greedyString())
|
||||
.suggests((context, builder) -> getSuggestions(context.getSource(), getArguments(context), builder))
|
||||
.executes(context -> executeCommand(context.getSource(), getArguments(context)))
|
||||
.build();
|
||||
command.addChild(args);
|
||||
dispatcher.getRoot().addChild(command);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String[] getArguments(@NotNull CommandContext<CommandSourceStack> context) {
|
||||
String input = context.getInput();
|
||||
int firstSpace = input.indexOf(' ');
|
||||
if (firstSpace == -1) return new String[0];
|
||||
String rawArgs = input.substring(firstSpace + 1);
|
||||
String[] args = rawArgs.split(" ");
|
||||
if (rawArgs.endsWith(" ")) {
|
||||
args = Arrays.copyOf(args, args.length+1);
|
||||
args[args.length-1] = "";
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
boolean hasReloadPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
|
||||
boolean hasAdminPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
|
||||
source.sendSystemMessage(((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
|
||||
}
|
||||
} else {
|
||||
if (source.getEntity() == null) {
|
||||
TAB.getInstance().getCommand().execute(null, args);
|
||||
} else {
|
||||
TabPlayer player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
|
||||
if (player != null) TAB.getInstance().getCommand().execute(player, args);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args,
|
||||
@NotNull SuggestionsBuilder builder) {
|
||||
TabPlayer player = null;
|
||||
if (source.getEntity() != null) {
|
||||
player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
|
||||
if (player == null) return Suggestions.empty();
|
||||
}
|
||||
|
||||
SuggestionsBuilder newBuilder = builder;
|
||||
int lastSpace = newBuilder.getRemaining().lastIndexOf(' ');
|
||||
if (lastSpace != -1) {
|
||||
newBuilder = newBuilder.createOffset(lastSpace + 1 + newBuilder.getStart());
|
||||
}
|
||||
for (String suggestion : TAB.getInstance().getCommand().complete(player, args)) {
|
||||
newBuilder.suggest(suggestion);
|
||||
}
|
||||
return newBuilder.buildFuture();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList {
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
player.sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME),
|
||||
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), new Builder(entry).setLatency(latency)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE), new Builder(entry).setGameMode(gameMode)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
player.sendPacket(build(EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class), new Builder(entry.getUniqueId())
|
||||
.setName(entry.getName())
|
||||
.setSkin(entry.getSkin())
|
||||
.setGameMode(entry.getGameMode())
|
||||
.setLatency(entry.getLatency())
|
||||
.setDisplayName(entry.getDisplayName() == null ? null : player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.getPlayer().connection.send(new ClientboundTabListPacket(
|
||||
player.getPlatform().toComponent(header, player.getVersion()),
|
||||
player.getPlatform().toComponent(footer, player.getVersion()))
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Packet<?> build(@NotNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, @NotNull FabricTabList.Builder entry) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry.getId(),
|
||||
entry.createProfile(),
|
||||
true,
|
||||
entry.getLatency(),
|
||||
GameType.byId(entry.getGameMode()),
|
||||
entry.getDisplayName(),
|
||||
null
|
||||
));
|
||||
return packet;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public static class Builder {
|
||||
|
||||
@NotNull private final UUID id;
|
||||
@Nullable private String name;
|
||||
@Nullable private Skin skin;
|
||||
private int latency;
|
||||
private int gameMode;
|
||||
@Nullable private Component displayName;
|
||||
|
||||
@NotNull
|
||||
public Builder setName(@Nullable String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Builder setSkin(@Nullable Skin skin) {
|
||||
this.skin = skin;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Builder setLatency(int latency) {
|
||||
this.latency = latency;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Builder setGameMode(int gameMode) {
|
||||
this.gameMode = gameMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Builder setDisplayName(@Nullable Component displayName) {
|
||||
this.displayName = displayName;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GameProfile createProfile() {
|
||||
if (name == null) return null;
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.backend.entityview.EntityView;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@Getter
|
||||
public class FabricTabPlayer extends BackendTabPlayer {
|
||||
|
||||
@NotNull
|
||||
private final Scoreboard<FabricTabPlayer> scoreboard = new FabricScoreboard(this);
|
||||
|
||||
@NotNull
|
||||
private final TabList tabList = new FabricTabList(this);
|
||||
|
||||
@NotNull
|
||||
private final BossBar bossBar = new FabricBossBar(this);
|
||||
|
||||
@NotNull
|
||||
private final EntityView entityView = new FabricEntityView(this);
|
||||
|
||||
public FabricTabPlayer(@NotNull FabricPlatform platform, @NotNull ServerPlayer player) {
|
||||
super(platform, player, player.getUUID(), player.getGameProfile().getName(), player.level().dimension().location().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String permission) {
|
||||
return FabricTAB.hasPermission(getPlayer().createCommandSourceStack(), permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
return getPlayer().connection.latency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
getPlayer().sendSystemMessage(getPlatform().toComponent(message, getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TabList.Skin getSkin() {
|
||||
Collection<Property> properties = getPlayer().getGameProfile().getProperties().get(TabList.TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property skinProperty = properties.iterator().next();
|
||||
return new TabList.Skin(skinProperty.value(), skinProperty.signature());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ServerPlayer getPlayer() {
|
||||
return (ServerPlayer) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FabricPlatform getPlatform() {
|
||||
return (FabricPlatform) platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().gameMode.getGameModeForPlayer().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealth() {
|
||||
return getPlayer().getHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return getPlayer().getDisplayName().getString(); // Will make it work properly if someone asks
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends packet to the player
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
public void sendPacket(@NotNull Packet<?> packet) {
|
||||
getPlayer().connection.send(packet);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 550 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user