mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-13 12:42:06 -05:00
Compare commits
2300
Commits
2.8.11-pre12
...
4.0.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9da0db5f74 | ||
|
|
562a973c84 | ||
|
|
967980ddf7 | ||
|
|
81d3675a87 | ||
|
|
7be786bbaf | ||
|
|
f41c9be1f7 | ||
|
|
e88062d028 | ||
|
|
edb2828643 | ||
|
|
58c189cd0e | ||
|
|
012994847e | ||
|
|
d87801be5f | ||
|
|
6d78c0c104 | ||
|
|
3a83519122 | ||
|
|
a9f940ebed | ||
|
|
db73d9ef81 | ||
|
|
e2bd21c717 | ||
|
|
871215b7b5 | ||
|
|
a6153ef46f | ||
|
|
35475a6103 | ||
|
|
bf77ffc1a5 | ||
|
|
7a64a9a534 | ||
|
|
68e057b99c | ||
|
|
7338afff96 | ||
|
|
97ae7813aa | ||
|
|
c0f3667ac1 | ||
|
|
d5c5502abe | ||
|
|
194e8ffbd9 | ||
|
|
ef39458abd | ||
|
|
6a6907fb0a | ||
|
|
8bb7c4a4a6 | ||
|
|
91700e7ed0 | ||
|
|
661c2e6fc3 | ||
|
|
3d053a6149 | ||
|
|
34179e4f62 | ||
|
|
a23e5d9f50 | ||
|
|
1cbcbf70d9 | ||
|
|
0a7371aaec | ||
|
|
da13e2d861 | ||
|
|
5b18fa8c25 | ||
|
|
347231850a | ||
|
|
b1f9bdc26c | ||
|
|
03c9b95041 | ||
|
|
6e7e1d783b | ||
|
|
cfb9b0a029 | ||
|
|
b7ea6e5af6 | ||
|
|
44104df712 | ||
|
|
5de20d1c58 | ||
|
|
d156303740 | ||
|
|
6e36eb3554 | ||
|
|
f7f3891817 | ||
|
|
1ec28856ff | ||
|
|
9658e0b74d | ||
|
|
86cd8d4151 | ||
|
|
2418eb206e | ||
|
|
08fa715d26 | ||
|
|
52426de283 | ||
|
|
64b3da14c4 | ||
|
|
bf7341c52c | ||
|
|
e3490564a2 | ||
|
|
6218f0ed16 | ||
|
|
54e8e1ea98 | ||
|
|
4cd50fb3d5 | ||
|
|
590286a6c2 | ||
|
|
6c96650dcc | ||
|
|
7c83a74fd8 | ||
|
|
7b3f60cb3b | ||
|
|
bfc845088a | ||
|
|
e67d93a592 | ||
|
|
b518b980ae | ||
|
|
744bc1403a | ||
|
|
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 | ||
|
|
a7e4b99ce6 | ||
|
|
0463667270 | ||
|
|
d179bc20ae | ||
|
|
bc658a9223 | ||
|
|
e32e3baf3a | ||
|
|
4436f4cf42 | ||
|
|
1dc7b40ee9 | ||
|
|
fb2221d281 | ||
|
|
b5a295970b | ||
|
|
485a453b27 | ||
|
|
03d9ba9579 | ||
|
|
ead7adea31 | ||
|
|
9793fdfc06 | ||
|
|
60b73e5c0d | ||
|
|
ed374c83d1 | ||
|
|
f53828e01a | ||
|
|
91ab50fa6a | ||
|
|
bee0d9d086 | ||
|
|
b38f09f118 | ||
|
|
65f57e8bf1 | ||
|
|
e424e42e05 | ||
|
|
a22cd5338d | ||
|
|
f921737d1c | ||
|
|
2f760afc70 | ||
|
|
4ce56433f9 | ||
|
|
f8ace5fbb5 | ||
|
|
f89778e969 | ||
|
|
99f74d3824 | ||
|
|
dec9c42edc | ||
|
|
34365c13cb | ||
|
|
8a4c780fb1 | ||
|
|
bed49408da | ||
|
|
5dd400bdc5 | ||
|
|
07d5a03a95 | ||
|
|
d53e7665d7 | ||
|
|
4021d7cbac | ||
|
|
2634ca5f28 |
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,65 @@
|
||||
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
|
||||
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
|
||||
required: true
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Compatibility issue
|
||||
description: A compatibility issue with another plugin/mod or a specific version of a platform marked as supported
|
||||
labels: ["Type: Compatibility issue"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 4.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Software causing compatibility issue
|
||||
description: plugin/mod/platform version causing the issue
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Anything else you want to provide that will help in resolving the issue faster.
|
||||
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 ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: The compatibility wiki page does not mention this incompatibility
|
||||
required: true
|
||||
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
|
||||
required: true
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -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,49 +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.
|
||||
|
||||
|
||||
## Compiling
|
||||
Plugin can be compiled using maven.
|
||||
# About
|
||||
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
|
||||
similar plugins in terms of features, performance and compatibility.
|
||||
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
|
||||
|
||||
|
||||
## Premium code
|
||||
The source contains content of premium version as well. However, this must be activated by setting [this line to return true](https://github.com/NEZNAMY/TAB/blob/master/src/main/java/me/neznamy/tab/shared/TAB.java#L79).
|
||||
|
||||
|
||||
## Links
|
||||
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>
|
||||
# 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.
|
||||
|
||||
<dependencies>
|
||||
<!-- TAB -->
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>2.8.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
# 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,16 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
/**
|
||||
* An enum representing all available BossBar colors.
|
||||
* Calling ordinal() will return color's network ID.
|
||||
*/
|
||||
public enum BarColor {
|
||||
|
||||
PINK,
|
||||
BLUE,
|
||||
RED,
|
||||
GREEN,
|
||||
YELLOW,
|
||||
PURPLE,
|
||||
WHITE
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
/**
|
||||
* An enum representing all available BossBar styles.
|
||||
* Calling ordinal() will return style's network ID.
|
||||
*/
|
||||
public enum BarStyle {
|
||||
|
||||
PROGRESS,
|
||||
NOTCHED_6,
|
||||
NOTCHED_10,
|
||||
NOTCHED_12,
|
||||
NOTCHED_20
|
||||
}
|
||||
@@ -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.8-SNAPSHOT"
|
||||
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,180 @@
|
||||
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 = ((BukkitTabPlayer)receiver).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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogin(@NotNull Object packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@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,58 @@
|
||||
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(String.format(
|
||||
"%s[TAB] Your server version is not compatible. This plugin version was made for %s - %s. Disabling.",
|
||||
EnumChatFormat.RED.getFormat(), ProtocolVersion.V1_5, ProtocolVersion.LATEST_KNOWN_VERSION
|
||||
));
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[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,232 @@
|
||||
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.header.HeaderFooter;
|
||||
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
|
||||
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
}
|
||||
|
||||
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
|
||||
newPlayerInfoData = playerInfoDataClass.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
|
||||
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 (HeaderFooter.getInstance() != null) HeaderFooter.getInstance().set(player, header, footer);
|
||||
}
|
||||
|
||||
@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,72 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
sb.append(component.getModifier().getMagicCodes());
|
||||
if (component.getText() != null) sb.append(component.getText());
|
||||
for (IChatBaseComponent extra : component.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra, rgbClient));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
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 {
|
||||
|
||||
/** Color array for fast access */
|
||||
private static final org.bukkit.boss.BarColor[] colors = new org.bukkit.boss.BarColor[] {
|
||||
org.bukkit.boss.BarColor.PINK,
|
||||
org.bukkit.boss.BarColor.BLUE,
|
||||
org.bukkit.boss.BarColor.RED,
|
||||
org.bukkit.boss.BarColor.GREEN,
|
||||
org.bukkit.boss.BarColor.YELLOW,
|
||||
org.bukkit.boss.BarColor.PURPLE,
|
||||
org.bukkit.boss.BarColor.WHITE
|
||||
};
|
||||
|
||||
/** Style array for fast access */
|
||||
private static final org.bukkit.boss.BarStyle[] styles = new org.bukkit.boss.BarStyle[] {
|
||||
org.bukkit.boss.BarStyle.SOLID,
|
||||
org.bukkit.boss.BarStyle.SEGMENTED_6,
|
||||
org.bukkit.boss.BarStyle.SEGMENTED_10,
|
||||
org.bukkit.boss.BarStyle.SEGMENTED_12,
|
||||
org.bukkit.boss.BarStyle.SEGMENTED_20
|
||||
};
|
||||
|
||||
/** 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),
|
||||
colors[color.ordinal()],
|
||||
styles[style.ordinal()]
|
||||
);
|
||||
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(styles[style.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
bossBars.get(id).setColor(colors[color.ordinal()]);
|
||||
}
|
||||
|
||||
@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,89 @@
|
||||
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 {
|
||||
|
||||
/** Color array for fast access */
|
||||
private static final BossColor[] colors = new BossColor[] {
|
||||
BossColor.PINK,
|
||||
BossColor.BLUE,
|
||||
BossColor.RED,
|
||||
BossColor.GREEN,
|
||||
BossColor.YELLOW,
|
||||
BossColor.PURPLE,
|
||||
BossColor.WHITE
|
||||
};
|
||||
|
||||
/** Style array for fast access */
|
||||
private static final BossStyle[] styles = new BossStyle[] {
|
||||
BossStyle.SOLID,
|
||||
BossStyle.SEGMENTED_6,
|
||||
BossStyle.SEGMENTED_10,
|
||||
BossStyle.SEGMENTED_12,
|
||||
BossStyle.SEGMENTED_20
|
||||
};
|
||||
|
||||
/** 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,
|
||||
colors[color.ordinal()],
|
||||
styles[style.ordinal()]
|
||||
);
|
||||
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(styles[style.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
viaBossBars.get(id).setColor(colors[color.ordinal()]);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
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 me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"UnstableApiUsage", "deprecation"})
|
||||
public void unregisterExpansion() {
|
||||
if (ReflectionUtils.methodExists(PlaceholderExpansion.class, "unregister")) {
|
||||
// Added in 2.10.7 (Jul 28, 2020)
|
||||
unregister();
|
||||
} else {
|
||||
PlaceholderAPI.unregisterExpansion(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Header/footer sender using Bukkit API. Despite this feature getting
|
||||
* added into the game in 1.8, Bukkit team took 4 entire years to add
|
||||
* it to the API, which eventually made it there in 1.13. <p>
|
||||
* The input is just a String, meaning Bukkit will have to deserialize it
|
||||
* into components, which is a massive performance loss. It also does not
|
||||
* seem to support fonts.
|
||||
*/
|
||||
public class BukkitHeaderFooter extends HeaderFooter {
|
||||
|
||||
/** Flag tracking whether this class can be used on current server */
|
||||
@Getter
|
||||
private static final boolean available = ReflectionUtils.methodExists(Player.class, "setPlayerListHeaderFooter", String.class, String.class);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation") // Marked as deprecated by Paper to make us use their methods instead
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.getPlayer().setPlayerListHeaderFooter(
|
||||
BukkitUtils.toBukkitFormat(header, player.getVersion().getMinorVersion() >= 16),
|
||||
BukkitUtils.toBukkitFormat(footer, player.getVersion().getMinorVersion() >= 16)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
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 org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A class responsible for sending header/footer to players on request.
|
||||
*/
|
||||
public abstract class HeaderFooter {
|
||||
|
||||
/** Instance of the class, null if no implementation is available */
|
||||
@Getter
|
||||
@Nullable
|
||||
private static HeaderFooter instance;
|
||||
|
||||
/**
|
||||
* Finds the best available instance for current server software.
|
||||
*/
|
||||
public static void findInstance() {
|
||||
if (PaperHeaderFooter.isAvailable()) {
|
||||
instance = new PaperHeaderFooter();
|
||||
} else if (PacketHeaderFooter.isAvailable()) {
|
||||
instance = new PacketHeaderFooter();
|
||||
} else if (BukkitHeaderFooter.isAvailable()) {
|
||||
instance = new BukkitHeaderFooter();
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"sending Header/Footer due to a compatibility error. Using fallback solution using Bukkit API. " +
|
||||
"This will drastically drop performance, as well as miss support for fonts (added in 1.16). " +
|
||||
"Please update the plugin with to version proper support for your server version.");
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"sending Header/Footer due to a compatibility error. No fallback solution was found either. This will " +
|
||||
"result in the feature not working. " +
|
||||
"Please update the plugin with to version proper support for your server version.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends header/footer to player.
|
||||
*
|
||||
* @param player
|
||||
* Player to send header/footer to.
|
||||
* @param header
|
||||
* Header to use.
|
||||
* @param footer
|
||||
* Footer to use.
|
||||
*/
|
||||
public abstract void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
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.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Header/footer sender that uses NMS to send packets. Available
|
||||
* on all versions since 1.8 when the feature was added into the game.
|
||||
*/
|
||||
public class PacketHeaderFooter extends HeaderFooter {
|
||||
|
||||
private static Constructor<?> newHeaderFooter;
|
||||
private static Field HEADER;
|
||||
private static Field FOOTER;
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
static {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
Class<?> IChatBaseComponent;
|
||||
Class<?> HeaderFooterClass;
|
||||
if (BukkitReflection.isMojangMapped()) {
|
||||
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
|
||||
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
|
||||
} else if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
|
||||
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
|
||||
} else {
|
||||
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
|
||||
HeaderFooterClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
|
||||
}
|
||||
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);
|
||||
}
|
||||
available = true;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// Print exception to find out what went wrong
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
Object headerComponent = player.getPlatform().toComponent(header, player.getVersion());
|
||||
Object footerComponent = player.getPlatform().toComponent(footer, player.getVersion());
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
player.sendPacket(newHeaderFooter.newInstance(headerComponent, footerComponent));
|
||||
} else {
|
||||
Object packet = newHeaderFooter.newInstance();
|
||||
HEADER.set(packet, headerComponent);
|
||||
FOOTER.set(packet, footerComponent);
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
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.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Header/footer sender using Paper's API added in 1.16.5.
|
||||
* Due to lack of component cache for adventure components in TAB,
|
||||
* there is a performance loss compared to using NMS. However, we do not
|
||||
* need to serialize/deserialize anything ourselves thanks to direct component
|
||||
* conversion, so in the end it is roughly the same.
|
||||
*/
|
||||
public class PaperHeaderFooter extends HeaderFooter {
|
||||
|
||||
/** Flag tracking whether this class can be used on current server */
|
||||
@Getter
|
||||
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
|
||||
ReflectionUtils.methodExists(Player.class, "sendPlayerListHeaderAndFooter", Component.class, Component.class);
|
||||
|
||||
@Override
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.getPlayer().sendPlayerListHeaderAndFooter(
|
||||
AdventureHook.toAdventureComponent(header, player.getVersion()),
|
||||
AdventureHook.toAdventureComponent(footer, player.getVersion())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,292 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.*;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
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();
|
||||
Class<?> Entity;
|
||||
if (BukkitReflection.isMojangMapped()) {
|
||||
Entity = Class.forName("net.minecraft.world.entity.Entity");
|
||||
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) {
|
||||
Entity = Class.forName("net.minecraft.world.entity.Entity");
|
||||
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 {
|
||||
Entity = BukkitReflection.getLegacyClass("Entity");
|
||||
DataWatcher = BukkitReflection.getLegacyClass("DataWatcher");
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcherObject = BukkitReflection.getLegacyClass("DataWatcherObject");
|
||||
DataWatcherRegistry = BukkitReflection.getLegacyClass("DataWatcherRegistry");
|
||||
DataWatcherSerializer = BukkitReflection.getLegacyClass("DataWatcherSerializer");
|
||||
}
|
||||
}
|
||||
if (minorVersion >= 7) {
|
||||
newDataWatcher = DataWatcher.getConstructor(Entity);
|
||||
} else {
|
||||
newDataWatcher = DataWatcher.getConstructor();
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcher_register = ReflectionUtils.getMethod(
|
||||
DataWatcher,
|
||||
new String[]{"define", "register", "a", "m_135372_"}, // {Bukkit 1.20.2+, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
|
||||
DataWatcherObject, Object.class
|
||||
);
|
||||
} else {
|
||||
DataWatcher_register = ReflectionUtils.getMethod(DataWatcher, new String[]{"func_75682_a", "a"}, int.class, Object.class); // {Thermos 1.7.10, Bukkit}
|
||||
}
|
||||
if (minorVersion >= 19) {
|
||||
DataWatcher_packDirty = ReflectionUtils.getMethod(DataWatcher, new String[] {"packDirty", "b"}); // {Mojang | 1.20.2+, 1.20.2-}
|
||||
}
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
DataWatcher_markDirty = ReflectionUtils.getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
|
||||
}
|
||||
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.is1_19_3Plus()) {
|
||||
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(DataWatcherRegistry, "OPTIONAL_COMPONENT", "g").get(null);
|
||||
if (BukkitReflection.is1_19_4Plus()) {
|
||||
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry, "BOOLEAN", "k").get(null);
|
||||
} else {
|
||||
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry, "BOOLEAN", "j").get(null);
|
||||
}
|
||||
} else {
|
||||
if (minorVersion >= 13) {
|
||||
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(DataWatcherRegistry,
|
||||
"OPTIONAL_COMPONENT", "f", "f_135032_").get(null); // Mohist 1.18.2
|
||||
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry,
|
||||
"BOOLEAN", "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(((BukkitPlatform)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,123 @@
|
||||
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.header.HeaderFooter;
|
||||
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);
|
||||
|
||||
// There is only supposed to be one, however there are exceptions:
|
||||
// #1 - CatServer adds another method
|
||||
// #2 - Random mods may perform deep hack into the server and add another one (see #1089)
|
||||
// Get first and hope for the best, alternatively players may not have correct skins in layout, but who cares
|
||||
getProfile = ReflectionUtils.getMethods(EntityHuman, GameProfile.class).get(0);
|
||||
}
|
||||
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();
|
||||
HeaderFooter.findInstance();
|
||||
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,410 @@
|
||||
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 Class<?> ClientboundBundlePacket;
|
||||
private static Field ClientboundBundlePacket_packets;
|
||||
|
||||
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();
|
||||
if (BukkitReflection.is1_19_4Plus()) {
|
||||
ClientboundBundlePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundBundlePacket");
|
||||
ClientboundBundlePacket_packets = ReflectionUtils.getOnlyField(ClientboundBundlePacket.getSuperclass(), Iterable.class);
|
||||
}
|
||||
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.getMinorVersion() >= 19) {
|
||||
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "d").get(null);
|
||||
} else {
|
||||
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundlePacket(@NotNull Object packet) {
|
||||
return BukkitReflection.is1_19_4Plus() && ClientboundBundlePacket.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
|
||||
return (Iterable<Object>) ClientboundBundlePacket_packets.get(bundlePacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,285 @@
|
||||
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 {
|
||||
|
||||
/** 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) {
|
||||
registerDummyPlaceholder(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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts internal component class to platform's component class
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param version
|
||||
* Game version to convert component for
|
||||
* @return Converted component
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
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.Limitations;
|
||||
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, Limitations.SCOREBOARD_TITLE_PRE_1_13), 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, Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
}
|
||||
|
||||
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
|
||||
team.setPrefix(transform(prefix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
|
||||
}
|
||||
|
||||
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
|
||||
team.setSuffix(transform(suffix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+432
@@ -0,0 +1,432 @@
|
||||
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;
|
||||
|
||||
// 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 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;
|
||||
|
||||
private static Enum[] scoreboardActions;
|
||||
private static Enum[] displaySlots;
|
||||
private static Enum[] nameVisibilities;
|
||||
private static Enum[] collisionRules;
|
||||
private static Enum[] healthDisplays;
|
||||
private static Enum[] chatFormats;
|
||||
|
||||
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;
|
||||
Class<Enum> enumChatFormatClass;
|
||||
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);
|
||||
displaySlots = (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);
|
||||
scoreboardActions = new Enum[] {
|
||||
Enum.valueOf(EnumScoreboardAction, "CHANGE"),
|
||||
Enum.valueOf(EnumScoreboardAction, "REMOVE")
|
||||
};
|
||||
chatFormats = (Enum[]) enumChatFormatClass.getMethod("values").invoke(null);
|
||||
} 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 >= 8) {
|
||||
nameVisibilities = new Enum[] {
|
||||
Enum.valueOf(EnumNameTagVisibility, "ALWAYS"),
|
||||
Enum.valueOf(EnumNameTagVisibility, "NEVER"),
|
||||
Enum.valueOf(EnumNameTagVisibility, "HIDE_FOR_OTHER_TEAMS"),
|
||||
Enum.valueOf(EnumNameTagVisibility, "HIDE_FOR_OWN_TEAM")
|
||||
};
|
||||
healthDisplays = new Enum[] {
|
||||
Enum.valueOf(EnumScoreboardHealthDisplay, "INTEGER"),
|
||||
Enum.valueOf(EnumScoreboardHealthDisplay, "HEARTS")
|
||||
};
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumTeamPush);
|
||||
collisionRules = (Enum[]) EnumTeamPush.getMethod("values").invoke(null); // <1.13 has wrong enum constant names
|
||||
}
|
||||
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);
|
||||
}
|
||||
ScoreboardScore_setScore = ReflectionUtils.getMethod(
|
||||
scoreboardScoreClass,
|
||||
new String[] {"func_96647_c", "setScore", "b", "c", "m_83402_"}, // {Thermos, 1.5.1 - 1.17.1 & 1.20.2+, 1.18 - 1.20.1, 1.5, Mohist 1.18.2}
|
||||
int.class
|
||||
);
|
||||
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[] {"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
|
||||
boolean.class
|
||||
);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[] {"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
|
||||
boolean.class
|
||||
);
|
||||
if (minorVersion >= 13) {
|
||||
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setPrefix", "b", "m_83360_", "setPlayerPrefix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
|
||||
IChatBaseComponent
|
||||
);
|
||||
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setSuffix", "c", "m_83365_", "setPlayerSuffix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
|
||||
IChatBaseComponent
|
||||
);
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[] {"func_96666_b", "setPrefix", "b"}, // {Thermos, 1.5.1+, 1.5}
|
||||
String.class
|
||||
);
|
||||
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[] {"func_96662_c", "setSuffix", "c"}, // {Thermos, 1.5.1+, 1.5}
|
||||
String.class
|
||||
);
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[] {"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
|
||||
EnumNameTagVisibility
|
||||
);
|
||||
}
|
||||
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 = displaySlots[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(title),
|
||||
healthDisplays[display.ordinal()]
|
||||
),
|
||||
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, healthDisplays[display.ordinal()]);
|
||||
}
|
||||
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(prefix));
|
||||
ScoreboardTeam_setSuffix.invoke(team, toComponent(suffix));
|
||||
ScoreboardTeam_setColor.invoke(team, chatFormats[EnumChatFormat.lastColorsOf(prefix).ordinal()]);
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix.invoke(team, prefix);
|
||||
ScoreboardTeam_setSuffix.invoke(team, suffix);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 8)
|
||||
ScoreboardTeam_setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
|
||||
if (BukkitReflection.getMinorVersion() >= 9)
|
||||
ScoreboardTeam_setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
|
||||
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(scoreboardActions[0], 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(scoreboardActions[1], 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(""),
|
||||
null
|
||||
);
|
||||
}
|
||||
return newScoreboardObjective.newInstance(
|
||||
null,
|
||||
objectiveName,
|
||||
IScoreboardCriteria_self.get(null)
|
||||
);
|
||||
}
|
||||
|
||||
private Object toComponent(String text) {
|
||||
return player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(text), 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,97 @@
|
||||
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 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);
|
||||
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
|
||||
packet.setColor(color.ordinal());
|
||||
packet.setDivision(style.ordinal());
|
||||
player.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);
|
||||
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
|
||||
player.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.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.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.sendPacket(packet);
|
||||
|
||||
colors.put(id, color.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
player.sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
|
||||
|
||||
colors.remove(id);
|
||||
styles.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
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) {
|
||||
TAB tab = TAB.getInstance();
|
||||
if (tab.isPluginDisabled()) return;
|
||||
tab.getCPUManager().runTask(() -> {
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player == null) {
|
||||
player = createPlayer(e.getPlayer());
|
||||
|
||||
// I don't think this is actually needed, but someone said it fixed geyser warn in console
|
||||
// Doing this won't hurt, so whatever
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
|
||||
player.getScoreboard().freeze();
|
||||
}
|
||||
|
||||
tab.getFeatureManager().onJoin(player);
|
||||
} else {
|
||||
tab.getFeatureManager().onServerChange(player.getUniqueId(), e.getPlayer().getServer().getInfo().getName());
|
||||
if (player.getVersion().getNetworkId() < ProtocolVersion.V1_20_2.getNetworkId()) {
|
||||
// For versions below 1.20.2 the tablist is already clean when this event is called
|
||||
// For 1.20.2+ this event is called before, so we listen to Login packet instead
|
||||
tab.getFeatureManager().onTabListClear(player);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.packet.*;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BungeeMultiVersion {
|
||||
|
||||
/**
|
||||
* Flag tracking if the current BungeeCord version contains changes made in build 1732.
|
||||
* This build changes "ScoreboardDisplay" field "position" from "byte" to "int".
|
||||
*/
|
||||
private static final boolean b1732Plus = ReflectionUtils.getFields(ScoreboardDisplay.class, int.class).size() > 0;
|
||||
|
||||
/**
|
||||
* Flag tracking if the current BungeeCord version contains changes made in build 1738.
|
||||
* This build adds "sendPacketQueued" method to UserConnection class.
|
||||
*/
|
||||
private static final boolean b1738Plus = ReflectionUtils.methodExists(UserConnection.class, "sendPacketQueued", DefinedPacket.class);
|
||||
|
||||
/**
|
||||
* Flag tracking if the current BungeeCord version contains changes made in build 1760.
|
||||
* This build changes all packet fields for displaying information from String to BaseComponent.
|
||||
*/
|
||||
private static final boolean b1760Plus = ReflectionUtils.methodExists(BossBar.class, "setTitle", BaseComponent.class);
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public static ScoreboardDisplay newScoreboardDisplay(int position, @NotNull String name) {
|
||||
if (b1732Plus) {
|
||||
return ScoreboardDisplay.class.getConstructor(int.class, String.class).newInstance(position, name);
|
||||
} else {
|
||||
return new ScoreboardDisplay((byte) position, name);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void sendPacket(@NotNull BungeeTabPlayer player, @NotNull DefinedPacket packet) {
|
||||
if (b1738Plus) {
|
||||
try {
|
||||
UserConnection.class.getDeclaredMethod("sendPacketQueued", DefinedPacket.class).invoke(player.getPlayer(), packet);
|
||||
} catch (InvocationTargetException BungeeCordBug) { // Since we use reflection we must catch this one
|
||||
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
|
||||
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
|
||||
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
|
||||
if (TAB.getInstance().getConfiguration().isDebugMode()) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to deliver packet to player " + player.getName() +
|
||||
" (online = " + player.getPlayer().isConnected() + "): " + packet, BungeeCordBug);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void setDisplayName(@NotNull Item item, @NotNull IChatBaseComponent displayName, @NotNull ProtocolVersion version) {
|
||||
if (b1760Plus) {
|
||||
Item.class.getMethod("setDisplayName", BaseComponent.class).invoke(item, toComponent(displayName, version));
|
||||
} else {
|
||||
item.setDisplayName(displayName.toString(version));
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public static PlayerListHeaderFooter newPlayerListHeaderFooter(@NotNull IChatBaseComponent header,
|
||||
@NotNull IChatBaseComponent footer, ProtocolVersion version) {
|
||||
if (b1760Plus) {
|
||||
return PlayerListHeaderFooter.class.getConstructor(BaseComponent.class, BaseComponent.class).newInstance(
|
||||
toComponent(header, version),
|
||||
toComponent(footer, version)
|
||||
);
|
||||
} else {
|
||||
return new PlayerListHeaderFooter(
|
||||
header.toString(version),
|
||||
footer.toString(version)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void setTitle(@NotNull BossBar bossBar, @NotNull String title, @NotNull ProtocolVersion version) {
|
||||
if (b1760Plus) {
|
||||
BossBar.class.getMethod("setTitle", BaseComponent.class).invoke(bossBar, toComponent(IChatBaseComponent.optimizedComponent(title), version));
|
||||
} else {
|
||||
bossBar.setTitle(IChatBaseComponent.optimizedComponent(title).toString(version));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public static ScoreboardObjective newScoreboardObjective(@NotNull String objective, @NotNull IChatBaseComponent title,
|
||||
@Nullable ScoreboardObjective.HealthDisplay healthDisplay, byte action,
|
||||
@NotNull ProtocolVersion version) {
|
||||
if (b1760Plus) {
|
||||
return ScoreboardObjective.class
|
||||
.getConstructor(String.class, BaseComponent.class, ScoreboardObjective.HealthDisplay.class, byte.class)
|
||||
.newInstance(objective, toComponent(title, version), healthDisplay, action);
|
||||
} else {
|
||||
return new ScoreboardObjective(objective, title.toString(version), healthDisplay, action);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public static Team newTeam(@NotNull String name, byte mode, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull String nameTagVisibility, @NotNull String collisionRule, int color,
|
||||
byte options, @Nullable String[] players, @NotNull ProtocolVersion version) {
|
||||
if (b1760Plus) {
|
||||
return Team.class.getConstructor(String.class, byte.class, BaseComponent.class, BaseComponent.class,
|
||||
BaseComponent.class, String.class, String.class, int.class, byte.class, String[].class)
|
||||
.newInstance(
|
||||
name,
|
||||
mode,
|
||||
toComponent(IChatBaseComponent.optimizedComponent(name), version),
|
||||
toComponent(IChatBaseComponent.optimizedComponent(prefix), version),
|
||||
toComponent(IChatBaseComponent.optimizedComponent(suffix), version),
|
||||
nameTagVisibility,
|
||||
collisionRule,
|
||||
color,
|
||||
options,
|
||||
players
|
||||
);
|
||||
} else {
|
||||
return new Team(
|
||||
name,
|
||||
mode,
|
||||
jsonOrRaw(name, version),
|
||||
jsonOrRaw(prefix, version),
|
||||
jsonOrRaw(suffix, version),
|
||||
nameTagVisibility,
|
||||
collisionRule,
|
||||
color,
|
||||
options,
|
||||
players
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param version
|
||||
* Protocol version to create json string for
|
||||
* @return serialized component for 1.13+ clients, cut string for 1.12-
|
||||
*/
|
||||
@NotNull
|
||||
private static String jsonOrRaw(@NotNull String text, @NotNull ProtocolVersion version) {
|
||||
if (version.getMinorVersion() >= 13) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toString(version);
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts internal component class to platform's component class
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param version
|
||||
* Game version to convert component for
|
||||
* @return Converted component
|
||||
*/
|
||||
private static BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
|
||||
TextComponent textComponent = new TextComponent(component.getText());
|
||||
ChatModifier modifier = component.getModifier();
|
||||
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 (!component.getExtra().isEmpty()) textComponent.setExtra(
|
||||
component.getExtra().stream().map(c -> toComponent(c, version)).collect(Collectors.toList()));
|
||||
return textComponent;
|
||||
}
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
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) BungeeMultiVersion.setDisplayName(item, newDisplayName, 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) BungeeMultiVersion.setDisplayName(item, newDisplayName, 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogin(@NotNull Object packet) {
|
||||
return packet instanceof Login;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,99 @@
|
||||
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.TAB;
|
||||
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.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.hook.ViaVersionHook;
|
||||
import me.neznamy.tab.shared.proxy.ProxyPlatform;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
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;
|
||||
|
||||
/**
|
||||
* BungeeCord implementation of Platform
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
@NotNull
|
||||
private final BungeeTAB plugin;
|
||||
|
||||
@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
|
||||
@Nullable
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return new BungeePipelineInjector();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerChannel() {
|
||||
ProxyServer.getInstance().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
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.ScoreboardObjective;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardScore;
|
||||
import net.md_5.bungee.protocol.packet.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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> {
|
||||
|
||||
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
player.sendPacket(BungeeMultiVersion.newScoreboardDisplay(slot.ordinal(), objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
|
||||
objectiveName,
|
||||
IChatBaseComponent.optimizedComponent(title),
|
||||
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
|
||||
(byte) 0,
|
||||
player.getVersion()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
|
||||
objectiveName,
|
||||
new IChatBaseComponent(), // Empty value instead of null to prevent NPE kick on 1.7
|
||||
null,
|
||||
(byte) 1,
|
||||
player.getVersion()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
|
||||
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
|
||||
objectiveName,
|
||||
IChatBaseComponent.optimizedComponent(title),
|
||||
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
|
||||
(byte) 2,
|
||||
player.getVersion()
|
||||
));
|
||||
}
|
||||
|
||||
@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.sendPacket(BungeeMultiVersion.newTeam(
|
||||
name,
|
||||
(byte) 0,
|
||||
prefix,
|
||||
suffix,
|
||||
visibility.toString(),
|
||||
collision.toString(),
|
||||
color,
|
||||
(byte)options,
|
||||
players.toArray(new String[0]),
|
||||
player.getVersion()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
player.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.sendPacket(BungeeMultiVersion.newTeam(
|
||||
name,
|
||||
(byte) 2,
|
||||
prefix,
|
||||
suffix,
|
||||
visibility.toString(),
|
||||
collision.toString(),
|
||||
color,
|
||||
(byte)options,
|
||||
null,
|
||||
player.getVersion()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
|
||||
}
|
||||
}
|
||||
@@ -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 " +
|
||||
"(released on December 7th, 2022) 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,169 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
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.TAB;
|
||||
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.chat.ComponentSerializer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
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(ComponentSerializer.parse(message.toString(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().isConnected()) return;
|
||||
try {
|
||||
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
|
||||
} catch (NullPointerException BungeeCordBug) {
|
||||
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
|
||||
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
|
||||
// at net.md_5.bungee.ServerConnection.sendPacketQueued(ServerConnection.java:48)
|
||||
// at net.md_5.bungee.ServerConnection.sendData(ServerConnection.java:70)
|
||||
if (TAB.getInstance().getConfiguration().isDebugMode()) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to deliver plugin message to player " + getName() +
|
||||
" (online = " + getPlayer().isConnected() + ")", BungeeCordBug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends packet to the player. If BungeeCord supports 1.20.2+, new packet queue method is used
|
||||
* to avoid error when sending packet in configuration phase.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void sendPacket(@NotNull DefinedPacket packet) {
|
||||
if (!getPlayer().isConnected()) return;
|
||||
BungeeMultiVersion.sendPacket(this, packet);
|
||||
}
|
||||
}
|
||||
+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);
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
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.tab.ServerUnique;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class BungeeTabList implements TabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
@NotNull
|
||||
protected final BungeeTabPlayer player;
|
||||
|
||||
/** Pointer to UUIDs in player's TabList */
|
||||
private final Collection<UUID> uuids;
|
||||
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public BungeeTabList(@NotNull BungeeTabPlayer player) {
|
||||
this.player = player;
|
||||
uuids = (Collection<UUID>) ReflectionUtils.getField(ServerUnique.class, "uuids").get(((UserConnection)player.getPlayer()).getTabListHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.sendPacket(BungeeMultiVersion.newPlayerListHeaderFooter(header, footer, player.getVersion()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Item item(@NotNull UUID id) {
|
||||
Item item = new Item();
|
||||
item.setUuid(id);
|
||||
return item;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Item entryToItem(Entry entry) {
|
||||
Item item = item(entry.getUniqueId());
|
||||
if (entry.getDisplayName() != null) {
|
||||
BungeeMultiVersion.setDisplayName(item, entry.getDisplayName(), 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());
|
||||
return item;
|
||||
}
|
||||
|
||||
public void addUuid(UUID id) {
|
||||
uuids.add(id);
|
||||
}
|
||||
|
||||
public void removeUuid(UUID id) {
|
||||
uuids.remove(id);
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
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.
|
||||
*/
|
||||
public class BungeeTabList1193 extends BungeeTabList {
|
||||
|
||||
public BungeeTabList1193(@NotNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
removeUuid(entry);
|
||||
PlayerListItemRemove remove = new PlayerListItemRemove();
|
||||
remove.setUuids(new UUID[]{entry});
|
||||
player.sendPacket(remove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
if (displayName != null) BungeeMultiVersion.setDisplayName(item, displayName, 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) {
|
||||
addUuid(entry.getUniqueId());
|
||||
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), entryToItem(entry));
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull EnumSet<PlayerListItemUpdate.Action> actions, @NotNull Item item) {
|
||||
PlayerListItemUpdate packet = new PlayerListItemUpdate();
|
||||
packet.setActions(actions);
|
||||
packet.setItems(new Item[]{item});
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
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.
|
||||
* While BungeeCord itself does not support 1.7, some of its forks do.
|
||||
* This was tested on FlameCord fork.
|
||||
*/
|
||||
public class BungeeTabList17 extends BungeeTabList {
|
||||
|
||||
/** 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<>();
|
||||
|
||||
public BungeeTabList17(@NotNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
removeUuid(entry);
|
||||
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) {
|
||||
addUuid(entry.getUniqueId());
|
||||
String displayNameString = entry.getDisplayName() == null ? String.valueOf(entry.getName()) : entry.getDisplayName().toLegacyText();
|
||||
if (displayNameString.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7)
|
||||
displayNameString = displayNameString.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
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});
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
|
||||
PlayerListItem.Item item = new PlayerListItem.Item();
|
||||
item.setUsername(username);
|
||||
BungeeMultiVersion.setDisplayName(item, IChatBaseComponent.optimizedComponent(displayName), player.getVersion());
|
||||
item.setPing(latency);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
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.
|
||||
*/
|
||||
public class BungeeTabList18 extends BungeeTabList {
|
||||
|
||||
public BungeeTabList18(@NotNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
removeUuid(entry);
|
||||
sendPacket(PlayerListItem.Action.REMOVE_PLAYER, item(entry));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
if (displayName != null) BungeeMultiVersion.setDisplayName(item, displayName, 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) {
|
||||
addUuid(entry.getUniqueId());
|
||||
sendPacket(PlayerListItem.Action.ADD_PLAYER, entryToItem(entry));
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull PlayerListItem.Action action, @NotNull Item item) {
|
||||
PlayerListItem packet = new PlayerListItem();
|
||||
packet.setAction(action);
|
||||
packet.setItems(new Item[]{item});
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
@@ -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,74 @@
|
||||
val minecraftVersion = "1.20.2"
|
||||
|
||||
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 "0.28.5+1.14",
|
||||
)
|
||||
|
||||
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-command-api-v${if (minecraftVersion.split(".")[1].toInt() >= 19) "2" else "1"}", fabricApiVersions[minecraftVersion]))
|
||||
if (minecraftVersion.split(".")[1].toInt() >= 16) {
|
||||
modImplementation(fabricApi.module("fabric-entity-events-v1", 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,81 @@
|
||||
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.BossBarColor;
|
||||
import net.minecraft.world.BossEvent.BossBarOverlay;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class FabricBossBar implements BossBar {
|
||||
|
||||
/** Color array for fast access */
|
||||
private static final BossBarColor[] colors = new BossBarColor[] {
|
||||
BossBarColor.PINK,
|
||||
BossBarColor.BLUE,
|
||||
BossBarColor.RED,
|
||||
BossBarColor.GREEN,
|
||||
BossBarColor.YELLOW,
|
||||
BossBarColor.PURPLE,
|
||||
BossBarColor.WHITE
|
||||
};
|
||||
|
||||
/** Overlay array for fast access */
|
||||
private static final BossBarOverlay[] overlays = new BossBarOverlay[] {
|
||||
BossBarOverlay.PROGRESS,
|
||||
BossBarOverlay.NOTCHED_6,
|
||||
BossBarOverlay.NOTCHED_10,
|
||||
BossBarOverlay.NOTCHED_12,
|
||||
BossBarOverlay.NOTCHED_20
|
||||
};
|
||||
|
||||
@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()),
|
||||
colors[color.ordinal()],
|
||||
overlays[style.ordinal()]
|
||||
);
|
||||
FabricMultiVersion.setProgress(bar, 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) {
|
||||
FabricMultiVersion.setProgress(bars.get(id), progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
bars.get(id).setOverlay(overlays[style.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
bars.get(id).setColor(colors[color.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
bars.remove(id).removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
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.world.entity.Entity;
|
||||
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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(FabricMultiVersion.addEntity(entityId, id, entityType, location, data));
|
||||
updateEntityMetadata(entityId, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
player.sendPacket(FabricMultiVersion.newEntityMetadata(entityId, data));
|
||||
|
||||
}
|
||||
|
||||
@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) {
|
||||
FabricMultiVersion.destroyEntities(player, entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyPacket(@NotNull Object packet) {
|
||||
return FabricMultiVersion.isEntityDestroyPacket(packet);
|
||||
}
|
||||
|
||||
@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 FabricMultiVersion.getDestroyedEntities(destroyPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundlePacket(@NotNull Object packet) {
|
||||
return FabricMultiVersion.isBundlePacket(packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
|
||||
return (Iterable<Object>) (Object) FabricMultiVersion.getPackets(bundlePacket);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 562 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user