Compare commits
| 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 |
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,12 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://paypal.me/neznamy1'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -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
|
||||
+21
-12
@@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v4
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
jobs:
|
||||
@@ -10,27 +11,35 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- 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 11
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v1
|
||||
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 Maven packages
|
||||
uses: actions/cache@v1
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
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
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
|
||||
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
|
||||
+6
-1
@@ -33,5 +33,10 @@ hs_err_pid*
|
||||
.idea/
|
||||
|
||||
# other
|
||||
.gitattributes
|
||||
*.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,41 +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. Or just get premium version for free.
|
||||
|
||||
|
||||
## Compiling
|
||||
Plugin can be compiled using maven. Once you've purchased the premium version, you can enable premium features by setting [this line to return true](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/TAB.java#L82) and apply your code improvements. Enabling it without purchasing the plugin may result in the features not working correctly.
|
||||
**No support is provided for self-compiled jars, only for official compilations.** If you compiled it yourself, it is highly likely you modified something, which might be the reason why is the plugin not working. If you didn't modify anything, you can just get the plugin from official download locations.
|
||||
|
||||
|
||||
## 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
|
||||
# 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.
|
||||
|
||||
# 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.
|
||||
|
||||
## Maven Repository
|
||||
```
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
# 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.
|
||||
|
||||
<dependencies>
|
||||
<!-- TAB -->
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB</artifactId>
|
||||
<version>2.9.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
# 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")
|
||||
}
|
||||
}
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>me.neznamy</groupId>
|
||||
<artifactId>TAB</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</parent>
|
||||
<artifactId>bukkit</artifactId>
|
||||
|
||||
<repositories>
|
||||
|
||||
<!-- Spigot API -->
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
|
||||
<!-- PlaceholderAPI -->
|
||||
<repository>
|
||||
<id>placeholderapi</id>
|
||||
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
|
||||
</repository>
|
||||
|
||||
<!-- Vault -->
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
|
||||
<!-- ViaVersion -->
|
||||
<repository>
|
||||
<id>viaversion</id>
|
||||
<url>https://repo.viaversion.com/</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>aikar</id>
|
||||
<url>https://repo.aikar.co/content/groups/aikar/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- TAB Core -->
|
||||
<dependency>
|
||||
<groupId>me.neznamy</groupId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>YamlAssist</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.googlecode.json-simple</groupId>
|
||||
<artifactId>json-simple</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Spigot API -->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.17-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-chat</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- PlaceholderAPI -->
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.10.9</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Vault -->
|
||||
<dependency>
|
||||
<groupId>com.github.MilkBowl</groupId>
|
||||
<artifactId>VaultAPI</artifactId>
|
||||
<version>1.7</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>bukkit</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- ViaVersion -->
|
||||
<dependency>
|
||||
<groupId>com.viaversion</groupId>
|
||||
<artifactId>viaversion-api</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>authlib</artifactId>
|
||||
<version>1.11</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Bukkit Metrics -->
|
||||
<dependency>
|
||||
<groupId>org.bstats</groupId>
|
||||
<artifactId>bstats-bukkit</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,5 +1,10 @@
|
||||
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;
|
||||
@@ -7,66 +12,36 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The core for bukkit forwarding events into all enabled features
|
||||
*/
|
||||
public class BukkitEventListener implements Listener {
|
||||
public class BukkitEventListener extends EventListener<Player> implements Listener {
|
||||
|
||||
/**
|
||||
* Listener to PlayerQuitEvent to remove player data and forward the event to features
|
||||
* @param e quit event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e){
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask("processing PlayerQuitEvent", () -> TAB.getInstance().getFeatureManager().onQuit(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerJoinEvent to create player data and forward the event to features
|
||||
* @param e join event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask("processing PlayerJoinEvent", () -> {
|
||||
try {
|
||||
int i = 0;
|
||||
int version;
|
||||
while ((version = Main.getProtocolVersion(e.getPlayer())) == -1 && ++i < 100) {
|
||||
Thread.sleep(2);
|
||||
}
|
||||
if (!e.getPlayer().isOnline()) return;
|
||||
if (i == 50) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get protocol version of " + e.getPlayer().getName() + " after 50 retries");
|
||||
}
|
||||
TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), version));
|
||||
} catch (InterruptedException pluginDisabled) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
}
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e) {
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerChangedWorldEvent to forward the event to features
|
||||
* @param e world changed event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e){
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask("processing PlayerChangedWorldEvent", () -> TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
|
||||
}
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
join(e.getPlayer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerChangedWorldEvent to forward the event to features
|
||||
* @param e command preprocess event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onCommand(PlayerCommandPreprocessEvent e) {
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
if (TAB.getInstance().getFeatureManager().onCommand(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -1,595 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import com.viaversion.viaversion.api.type.Type;
|
||||
import com.viaversion.viaversion.libs.gson.JsonParser;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.packets.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.packets.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.packets.IChatBaseComponent.ClickAction;
|
||||
import me.neznamy.tab.shared.packets.PacketBuilder;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutBoss;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutBoss.Action;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutChat;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.EnumGamemode;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerListHeaderFooter;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardDisplayObjective;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardScore;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardTeam;
|
||||
import me.neznamy.tab.shared.rgb.TextColor;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class BukkitPacketBuilder implements PacketBuilder {
|
||||
|
||||
//nms storage
|
||||
private NMSStorage nms;
|
||||
|
||||
//entity type ids
|
||||
private EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
* @param nms - nms storage
|
||||
*/
|
||||
public BukkitPacketBuilder(NMSStorage nms) {
|
||||
this.nms = nms;
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 1);
|
||||
entityIds.put(EntityType.WITHER, 83);
|
||||
} else {
|
||||
entityIds.put(EntityType.WITHER, 64);
|
||||
if (nms.getMinorVersion() >= 8){
|
||||
entityIds.put(EntityType.ARMOR_STAND, 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
//1.9+ server, handled using bukkit api
|
||||
return packet;
|
||||
}
|
||||
if (clientVersion.getMinorVersion() >= 9 && Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) {
|
||||
//1.9+ client on 1.8 server
|
||||
//technically redundant VV check as there is no other way to get 1.9 client on 1.8 server
|
||||
return buildBossPacketVia(packet, clientVersion);
|
||||
}
|
||||
|
||||
//<1.9 client and server
|
||||
return buildBossPacketEntity(packet, clientVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds 1.9 bossbar packet bytebuf using viaversion
|
||||
* @param packet - packet to build
|
||||
* @param clientVersion - client version
|
||||
* @return bytebuf with 1.9 bossbar packet content
|
||||
*/
|
||||
private ByteBuf buildBossPacketVia(PacketPlayOutBoss packet, ProtocolVersion clientVersion) {
|
||||
if (clientVersion == ProtocolVersion.UNKNOWN) return null; //preventing disconnect if packet ID changes and users do not update
|
||||
try {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
Type.VAR_INT.writePrimitive(buf, clientVersion.getMinorVersion() == 15 || clientVersion.getMinorVersion() >= 17 ? 0x0D : 0x0C);
|
||||
Type.UUID.write(buf, packet.getId());
|
||||
Type.VAR_INT.writePrimitive(buf, packet.getOperation().ordinal());
|
||||
switch (packet.getOperation()) {
|
||||
case ADD:
|
||||
Type.COMPONENT.write(buf, JsonParser.parseString(IChatBaseComponent.optimizedComponent(packet.getName()).toString(clientVersion)));
|
||||
Type.FLOAT.writePrimitive(buf, packet.getPct());
|
||||
Type.VAR_INT.writePrimitive(buf, packet.getColor().ordinal());
|
||||
Type.VAR_INT.writePrimitive(buf, packet.getOverlay().ordinal());
|
||||
Type.BYTE.write(buf, packet.getFlags());
|
||||
break;
|
||||
case REMOVE:
|
||||
break;
|
||||
case UPDATE_PCT:
|
||||
Type.FLOAT.writePrimitive(buf, packet.getPct());
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
Type.COMPONENT.write(buf, JsonParser.parseString(IChatBaseComponent.optimizedComponent(packet.getName()).toString(clientVersion)));
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
Type.VAR_INT.writePrimitive(buf, packet.getColor().ordinal());
|
||||
Type.VAR_INT.writePrimitive(buf, packet.getOverlay().ordinal());
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
Type.BYTE.write(buf, packet.getFlags());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return buf;
|
||||
} catch (Exception t) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to create 1.9 bossbar packet using ViaVersion v" + Bukkit.getPluginManager().getPlugin("ViaVersion").getDescription().getVersion() + ". Is it the latest version?", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity bossbar packet
|
||||
* @param packet - packet to build
|
||||
* @param clientVersion - client version
|
||||
* @return entity bossbar packet
|
||||
* @throws InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
if (packet.getOperation() == Action.UPDATE_STYLE) return null; //nothing to do here
|
||||
|
||||
int entityId = packet.getId().hashCode();
|
||||
if (packet.getOperation() == Action.REMOVE) {
|
||||
return buildEntityDestroyPacket(entityId);
|
||||
}
|
||||
DataWatcher w = new DataWatcher();
|
||||
if (packet.getOperation() == Action.UPDATE_PCT || packet.getOperation() == Action.ADD) {
|
||||
float health = 300*packet.getPct();
|
||||
if (health == 0) health = 1;
|
||||
w.helper().setHealth(health);
|
||||
}
|
||||
if (packet.getOperation() == Action.UPDATE_NAME || packet.getOperation() == Action.ADD) {
|
||||
w.helper().setCustomName(packet.getName(), clientVersion);
|
||||
}
|
||||
if (packet.getOperation() == Action.ADD) {
|
||||
w.helper().setEntityFlags((byte) 32);
|
||||
return buildEntitySpawnPacket(entityId, null, EntityType.WITHER, new Location(null, 0,0,0), w);
|
||||
} else {
|
||||
return buildEntityMetadataPacket(entityId, w);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException {
|
||||
Object component = stringToComponent(packet.getMessage().toString(clientVersion));
|
||||
Constructor<?> c = nms.getConstructor("PacketPlayOutChat");
|
||||
if (nms.getMinorVersion() >= 16) {
|
||||
return c.newInstance(component, nms.getEnum("ChatMessageType")[packet.getType().ordinal()], UUID.randomUUID());
|
||||
}
|
||||
if (nms.getMinorVersion() >= 12) {
|
||||
return c.newInstance(component, Enum.valueOf((Class<Enum>) nms.getClass("ChatMessageType"), packet.getType().toString()));
|
||||
}
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
return c.newInstance(component, (byte) packet.getType().ordinal());
|
||||
}
|
||||
if (nms.getMinorVersion() == 7) {
|
||||
return c.newInstance(component);
|
||||
}
|
||||
throw new IllegalStateException("Not supported on <1.7");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException, NegativeArraySizeException {
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
Object nmsPacket = nms.getConstructor("PacketPlayOutPlayerInfo").newInstance(nms.getEnum("EnumPlayerInfoAction")[packet.getAction().ordinal()], Array.newInstance(nms.getClass("EntityPlayer"), 0));
|
||||
List<Object> items = new ArrayList<>();
|
||||
for (PlayerInfoData data : packet.getEntries()) {
|
||||
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
|
||||
|
||||
if (data.getSkin() != null) profile.getProperties().putAll((PropertyMap) data.getSkin());
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (nms.getConstructor("PlayerInfoData").getParameterCount() == 5) {
|
||||
parameters.add(nmsPacket);
|
||||
}
|
||||
parameters.add(profile);
|
||||
parameters.add(data.getLatency());
|
||||
parameters.add(data.getGameMode() == null ? null : nms.getEnum("EnumGamemode")[nms.getEnum("EnumGamemode").length-EnumGamemode.values().length+data.getGameMode().ordinal()]); //not_set was removed in 1.17
|
||||
parameters.add(data.getDisplayName() == null ? null : toNMSComponent(data.getDisplayName(), clientVersion));
|
||||
items.add(nms.getConstructor("PlayerInfoData").newInstance(parameters.toArray()));
|
||||
}
|
||||
nms.setField(nmsPacket, "PacketPlayOutPlayerInfo_PLAYERS", items);
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
return nms.getConstructor("PacketPlayOutPlayerListHeaderFooter").newInstance(toNMSComponent(packet.getHeader(), clientVersion), toNMSComponent(packet.getFooter(), clientVersion));
|
||||
}
|
||||
Object nmsPacket = nms.getConstructor("PacketPlayOutPlayerListHeaderFooter").newInstance();
|
||||
nms.setField(nmsPacket, "PacketPlayOutPlayerListHeaderFooter_HEADER", toNMSComponent(packet.getHeader(), clientVersion));
|
||||
nms.setField(nmsPacket, "PacketPlayOutPlayerListHeaderFooter_FOOTER", toNMSComponent(packet.getFooter(), clientVersion));
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
return nms.getConstructor("PacketPlayOutScoreboardDisplayObjective").newInstance(packet.getSlot(), newScoreboardObjective(packet.getObjectiveName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException {
|
||||
String displayName = clientVersion.getMinorVersion() < 13 ? cutTo(packet.getDisplayName(), 32) : packet.getDisplayName();
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return nms.getConstructor("PacketPlayOutScoreboardObjective").newInstance(nms.getConstructor("ScoreboardObjective").newInstance(null, packet.getObjectiveName(), null,
|
||||
toNMSComponent(IChatBaseComponent.optimizedComponent(displayName), clientVersion),
|
||||
packet.getRenderType() == null ? null : nms.getEnum("EnumScoreboardHealthDisplay")[packet.getRenderType().ordinal()]),
|
||||
packet.getMethod());
|
||||
}
|
||||
|
||||
Object nmsPacket = nms.getConstructor("PacketPlayOutScoreboardObjective").newInstance();
|
||||
nms.setField(nmsPacket, "PacketPlayOutScoreboardObjective_OBJECTIVENAME", packet.getObjectiveName());
|
||||
nms.setField(nmsPacket, "PacketPlayOutScoreboardObjective_DISPLAYNAME", displayName);
|
||||
if (nms.getMinorVersion() >= 8 && packet.getRenderType() != null) {
|
||||
nms.setField(nmsPacket, "PacketPlayOutScoreboardObjective_RENDERTYPE", Enum.valueOf((Class<Enum>) nms.getClass("EnumScoreboardHealthDisplay"), packet.getRenderType().toString()));
|
||||
}
|
||||
nms.setField(nmsPacket, "PacketPlayOutScoreboardObjective_METHOD", packet.getMethod());
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return nms.getConstructor("PacketPlayOutScoreboardScore_1_13").newInstance(nms.getEnum("EnumScoreboardAction")[packet.getAction().ordinal()], packet.getObjectiveName(), packet.getPlayer(), packet.getScore());
|
||||
}
|
||||
if (packet.getAction() == PacketPlayOutScoreboardScore.Action.REMOVE) {
|
||||
return nms.getConstructor("PacketPlayOutScoreboardScore_String").newInstance(packet.getPlayer());
|
||||
}
|
||||
Object score = nms.getConstructor("ScoreboardScore").newInstance(nms.getConstructor("Scoreboard").newInstance(), newScoreboardObjective(packet.getObjectiveName()), packet.getPlayer());
|
||||
nms.getMethod("ScoreboardScore_setScore").invoke(score, packet.getScore());
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
return nms.getConstructor("PacketPlayOutScoreboardScore").newInstance(score);
|
||||
}
|
||||
return nms.getConstructor("PacketPlayOutScoreboardScore").newInstance(score, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException {
|
||||
Object team;
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
team = createTeamModern(packet, clientVersion);
|
||||
} else {
|
||||
team = createTeamLegacy(packet, clientVersion);
|
||||
}
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
switch (packet.getMethod()) {
|
||||
case 0:
|
||||
return nms.getMethod("PacketPlayOutScoreboardTeam_ofBoolean").invoke(null, team, true);
|
||||
case 1:
|
||||
return nms.getMethod("PacketPlayOutScoreboardTeam_of").invoke(null, team);
|
||||
case 2:
|
||||
return nms.getMethod("PacketPlayOutScoreboardTeam_ofBoolean").invoke(null, team, false);
|
||||
case 3:
|
||||
return nms.getMethod("PacketPlayOutScoreboardTeam_ofString").invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.getEnum("PacketPlayOutScoreboardTeam_a")[0]);
|
||||
case 4:
|
||||
return nms.getMethod("PacketPlayOutScoreboardTeam_ofString").invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.getEnum("PacketPlayOutScoreboardTeam_a")[1]);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid action: " + packet.getMethod());
|
||||
}
|
||||
}
|
||||
return nms.getConstructor("PacketPlayOutScoreboardTeam").newInstance(team, packet.getMethod());
|
||||
}
|
||||
|
||||
private Object createTeamModern(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException {
|
||||
String prefix = packet.getPlayerPrefix();
|
||||
String suffix = packet.getPlayerSuffix();
|
||||
if (clientVersion.getMinorVersion() < 13) {
|
||||
prefix = cutTo(prefix, 16);
|
||||
suffix = cutTo(suffix, 16);
|
||||
}
|
||||
Object team = nms.getConstructor("ScoreboardTeam").newInstance(nms.getConstructor("Scoreboard").newInstance(), packet.getName());
|
||||
((Collection<String>)nms.getMethod("ScoreboardTeam_getPlayerNameSet").invoke(team)).addAll(packet.getPlayers());
|
||||
if (prefix != null && prefix.length() > 0) nms.getMethod("ScoreboardTeam_setPrefix").invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
|
||||
if (suffix != null && suffix.length() > 0) nms.getMethod("ScoreboardTeam_setSuffix").invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
|
||||
EnumChatFormat format = packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(prefix);
|
||||
nms.getMethod("ScoreboardTeam_setColor").invoke(team, nms.getEnum("EnumChatFormat")[format.ordinal()]);
|
||||
nms.getMethod("ScoreboardTeam_setNameTagVisibility").invoke(team, String.valueOf(packet.getNametagVisibility()).equals("always") ? nms.getEnum("EnumNameTagVisibility")[0] : nms.getEnum("EnumNameTagVisibility")[1]);
|
||||
nms.getMethod("ScoreboardTeam_setCollisionRule").invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.getEnum("EnumTeamPush")[0] : nms.getEnum("EnumTeamPush")[1]);
|
||||
return team;
|
||||
}
|
||||
|
||||
private Object createTeamLegacy(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws IllegalAccessException, InvocationTargetException, InstantiationException, SecurityException {
|
||||
String prefix = packet.getPlayerPrefix();
|
||||
String suffix = packet.getPlayerSuffix();
|
||||
if (clientVersion.getMinorVersion() < 13) {
|
||||
prefix = cutTo(prefix, 16);
|
||||
suffix = cutTo(suffix, 16);
|
||||
}
|
||||
Object team = nms.getConstructor("ScoreboardTeam").newInstance(nms.getConstructor("Scoreboard").newInstance(), packet.getName());
|
||||
((Collection<String>)nms.getMethod("ScoreboardTeam_getPlayerNameSet").invoke(team)).addAll(packet.getPlayers());
|
||||
if (prefix != null) nms.getMethod("ScoreboardTeam_setPrefix").invoke(team, prefix);
|
||||
if (suffix != null) nms.getMethod("ScoreboardTeam_setSuffix").invoke(team, suffix);
|
||||
if (nms.getMinorVersion() >= 8) nms.getMethod("ScoreboardTeam_setNameTagVisibility").invoke(team, String.valueOf(packet.getNametagVisibility()).equals("always") ? nms.getEnum("EnumNameTagVisibility")[0] : nms.getEnum("EnumNameTagVisibility")[1]);
|
||||
if (nms.getMinorVersion() >= 9) nms.getMethod("ScoreboardTeam_setCollisionRule").invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.getEnum("EnumTeamPush")[0] : nms.getEnum("EnumTeamPush")[1]);
|
||||
return team;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity destroy packet with given parameter
|
||||
* @param id - entity id to destroy
|
||||
* @return destroy packet
|
||||
* @throws InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
public Object buildEntityDestroyPacket(int id) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
try {
|
||||
return nms.getConstructor("PacketPlayOutEntityDestroy").newInstance(new int[] {id});
|
||||
} catch (IllegalArgumentException e) {
|
||||
//1.17.0
|
||||
return nms.getConstructor("PacketPlayOutEntityDestroy").newInstance(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity metadata packet with given parameters
|
||||
* @param entityId - entity id
|
||||
* @param dataWatcher - datawatcher
|
||||
* @return metadata packet
|
||||
* @throws InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
public Object buildEntityMetadataPacket(int entityId, DataWatcher dataWatcher) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
return nms.getConstructor("PacketPlayOutEntityMetadata").newInstance(entityId, dataWatcher.toNMS(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity spawn packet with given parameters
|
||||
* @param entityId - entity id
|
||||
* @param uuid - entity uuid
|
||||
* @param entityType - entity type
|
||||
* @param loc - location to spawn at
|
||||
* @param dataWatcher - datawatcher
|
||||
* @return entity spawn packet
|
||||
* @throws IllegalAccessException
|
||||
* @throws InvocationTargetException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
public Object buildEntitySpawnPacket(int entityId, UUID uuid, EntityType entityType, Location loc, DataWatcher dataWatcher) throws IllegalAccessException, InstantiationException, InvocationTargetException {
|
||||
Object nmsPacket = nms.getConstructor("PacketPlayOutSpawnEntityLiving").newInstance(nms.getMethod("getHandle").invoke(Bukkit.getOnlinePlayers().iterator().next()));
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_ENTITYID", entityId);
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_ENTITYTYPE", entityIds.get(entityType));
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_YAW", (byte)(loc.getYaw() * 256.0f / 360.0f));
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_PITCH", (byte)(loc.getPitch() * 256.0f / 360.0f));
|
||||
if (nms.getMinorVersion() <= 14) {
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_DATAWATCHER", dataWatcher.toNMS());
|
||||
}
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_UUID", uuid);
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_X", loc.getX());
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_Y", loc.getY());
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_Z", loc.getZ());
|
||||
} else {
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_X", floor(loc.getX()*32));
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_Y", floor(loc.getY()*32));
|
||||
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_Z", floor(loc.getZ()*32));
|
||||
}
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity teleport packet with given parameters
|
||||
* @param entityId - entity id
|
||||
* @param location - location to teleport to
|
||||
* @return entity teleport packet
|
||||
* @throws IllegalAccessException
|
||||
* @throws InvocationTargetException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
public Object buildEntityTeleportPacket(int entityId, Location location) throws IllegalAccessException, InstantiationException, InvocationTargetException {
|
||||
Object nmsPacket = nms.getConstructor("PacketPlayOutEntityTeleport").newInstance(nms.getMethod("getHandle").invoke(Bukkit.getOnlinePlayers().iterator().next()));
|
||||
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_ENTITYID", entityId);
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_X", location.getX());
|
||||
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_Y", location.getY());
|
||||
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_Z", location.getZ());
|
||||
} else {
|
||||
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_X", floor(location.getX()*32));
|
||||
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_Y", floor(location.getY()*32));
|
||||
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_Z", floor(location.getZ()*32));
|
||||
}
|
||||
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_YAW", (byte) (location.getYaw()/360*256));
|
||||
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_PITCH", (byte) (location.getPitch()/360*256));
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws IllegalAccessException, InvocationTargetException {
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
EnumPlayerInfoAction action = EnumPlayerInfoAction.valueOf(nms.getField("PacketPlayOutPlayerInfo_ACTION").get(nmsPacket).toString());
|
||||
List<PlayerInfoData> listData = new ArrayList<>();
|
||||
for (Object nmsData : (List<?>) nms.getField("PacketPlayOutPlayerInfo_PLAYERS").get(nmsPacket)) {
|
||||
Object nmsGamemode = nms.getMethod("PlayerInfoData_getGamemode").invoke(nmsData);
|
||||
EnumGamemode gamemode = (nmsGamemode == null) ? null : EnumGamemode.valueOf(nmsGamemode.toString());
|
||||
GameProfile profile = (GameProfile) nms.getMethod("PlayerInfoData_getProfile").invoke(nmsData);
|
||||
Object nmsComponent = nms.getMethod("PlayerInfoData_getDisplayName").invoke(nmsData);
|
||||
IChatBaseComponent listName = nmsComponent == null ? null : fromNMSComponent(nmsComponent);
|
||||
listData.add(new PlayerInfoData(profile.getName(), profile.getId(), profile.getProperties(), (int) nms.getMethod("PlayerInfoData_getLatency").invoke(nmsData), gamemode, listName));
|
||||
}
|
||||
return new PacketPlayOutPlayerInfo(action, listData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket, ProtocolVersion clientVersion) throws IllegalAccessException {
|
||||
String objective = (String) nms.getField("PacketPlayOutScoreboardObjective_OBJECTIVENAME").get(nmsPacket);
|
||||
String displayName;
|
||||
Object component = nms.getField("PacketPlayOutScoreboardObjective_DISPLAYNAME").get(nmsPacket);
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
IChatBaseComponent c = component == null ? null : fromNMSComponent(component);
|
||||
displayName = c == null ? null : c.toLegacyText();
|
||||
} else {
|
||||
displayName = (String) component;
|
||||
}
|
||||
EnumScoreboardHealthDisplay renderType = null;
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
Object nmsRender = nms.getField("PacketPlayOutScoreboardObjective_RENDERTYPE").get(nmsPacket);
|
||||
if (nmsRender != null) {
|
||||
renderType = EnumScoreboardHealthDisplay.valueOf(nmsRender.toString());
|
||||
}
|
||||
}
|
||||
int method = nms.getField("PacketPlayOutScoreboardObjective_METHOD").getInt(nmsPacket);
|
||||
return new PacketPlayOutScoreboardObjective(method, objective, displayName, renderType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket, ProtocolVersion clientVersion) throws IllegalAccessException {
|
||||
return new PacketPlayOutScoreboardDisplayObjective(
|
||||
nms.getField("PacketPlayOutScoreboardDisplayObjective_POSITION").getInt(nmsPacket),
|
||||
(String) nms.getField("PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME").get(nmsPacket)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A method yoinked from minecraft code used to convert double to int
|
||||
* @param paramDouble double value
|
||||
* @return int value
|
||||
*/
|
||||
private int floor(double paramDouble){
|
||||
int i = (int)paramDouble;
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts json string into a component
|
||||
* @param json json as string
|
||||
* @return NMS component
|
||||
* @throws InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
public Object stringToComponent(String json) throws IllegalAccessException, InvocationTargetException {
|
||||
if (json == null) return null;
|
||||
return nms.getMethod("ChatSerializer_DESERIALIZE").invoke(null, json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts minecraft IChatBaseComponent into TAB's component class. Currently does not support hover event.
|
||||
* @param component - component to convert
|
||||
* @return converted component
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
public IChatBaseComponent fromNMSComponent(Object component) throws IllegalAccessException {
|
||||
long time = System.nanoTime();
|
||||
IChatBaseComponent obj = fromNMSComponent0(component);
|
||||
TAB.getInstance().getCPUManager().addMethodTime("fromNMSComponent", System.nanoTime()-time);
|
||||
return obj;
|
||||
}
|
||||
|
||||
//separate method to prevent extras counting cpu again due to recursion and finally showing higher usage than real
|
||||
public IChatBaseComponent fromNMSComponent0(Object component) throws IllegalAccessException {
|
||||
if (!nms.getClass("ChatComponentText").isInstance(component)) return null; //paper
|
||||
IChatBaseComponent chat = new IChatBaseComponent((String) nms.getField("ChatComponentText_text").get(component));
|
||||
Object modifier = nms.getField("ChatBaseComponent_modifier").get(component);
|
||||
if (modifier != null) {
|
||||
Object color = nms.getField("ChatModifier_color").get(modifier);
|
||||
if (color != null) {
|
||||
if (nms.getMinorVersion() >= 16) {
|
||||
String name = (String) nms.getField("ChatHexColor_name").get(color);
|
||||
if (name != null) {
|
||||
//legacy code
|
||||
chat.setColor(new TextColor(EnumChatFormat.valueOf(name.toUpperCase())));
|
||||
} else {
|
||||
int rgb = (int) nms.getField("ChatHexColor_rgb").get(color);
|
||||
chat.setColor(new TextColor((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF));
|
||||
}
|
||||
} else {
|
||||
chat.setColor(new TextColor(EnumChatFormat.valueOf(((Enum)color).name())));
|
||||
}
|
||||
}
|
||||
chat.setBold((Boolean) nms.getField("ChatModifier_bold").get(modifier));
|
||||
chat.setItalic((Boolean) nms.getField("ChatModifier_italic").get(modifier));
|
||||
chat.setObfuscated((Boolean) nms.getField("ChatModifier_obfuscated").get(modifier));
|
||||
chat.setStrikethrough((Boolean) nms.getField("ChatModifier_strikethrough").get(modifier));
|
||||
chat.setUnderlined((Boolean) nms.getField("ChatModifier_underlined").get(modifier));
|
||||
Object clickEvent = nms.getField("ChatModifier_clickEvent").get(modifier);
|
||||
if (clickEvent != null) {
|
||||
chat.onClick(ClickAction.valueOf(nms.getField("ChatClickable_action").get(clickEvent).toString().toUpperCase()), (String) nms.getField("ChatClickable_value").get(clickEvent));
|
||||
}
|
||||
}
|
||||
for (Object extra : (List<Object>) nms.getField("ChatBaseComponent_extra").get(component)) {
|
||||
chat.addExtra(fromNMSComponent0(extra));
|
||||
}
|
||||
return chat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TAB's IChatBaseComponent into minecraft's component. Currently does not support hover event.
|
||||
* @param component - component to convert
|
||||
* @param clientVersion - client version used to decide RGB conversion
|
||||
* @return converted component
|
||||
* @throws InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
public Object toNMSComponent(IChatBaseComponent component, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
long time = System.nanoTime();
|
||||
Object obj = toNMSComponent0(component, clientVersion);
|
||||
TAB.getInstance().getCPUManager().addMethodTime("toNMSComponent", System.nanoTime()-time);
|
||||
return obj;
|
||||
}
|
||||
|
||||
//separate method to prevent extras counting cpu again due to recursion and finally showing higher usage than real
|
||||
private Object toNMSComponent0(IChatBaseComponent component, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
if (component == null) return null;
|
||||
Object chat = nms.getConstructor("ChatComponentText").newInstance(component.getText());
|
||||
Object modifier;
|
||||
if (nms.getMinorVersion() >= 16) {
|
||||
Object color = null;
|
||||
if (component.getColor() != null) {
|
||||
if (clientVersion.getMinorVersion() >= 16) {
|
||||
color = nms.getMethod("ChatHexColor_ofInt").invoke(null, (component.getColor().getRed() << 16) + (component.getColor().getGreen() << 8) + component.getColor().getBlue());
|
||||
} else {
|
||||
color = nms.getMethod("ChatHexColor_ofString").invoke(null, component.getColor().getLegacyColor().toString().toLowerCase());
|
||||
}
|
||||
}
|
||||
modifier = nms.getConstructor("ChatModifier").newInstance(
|
||||
color,
|
||||
component.getBold(),
|
||||
component.getItalic(),
|
||||
component.getUnderlined(),
|
||||
component.getStrikethrough(),
|
||||
component.getObfuscated(),
|
||||
component.getClickAction() == null ? null : nms.getConstructor("ChatClickable").newInstance(nms.getMethod("EnumClickAction_a").invoke(null, component.getClickAction().toString().toLowerCase()), component.getClickValue().toString()),
|
||||
null, //need to make it for 1.16
|
||||
null, null);
|
||||
} else {
|
||||
modifier = nms.getConstructor("ChatModifier").newInstance();
|
||||
if (component.getClickAction() != null){
|
||||
nms.setField(modifier, "ChatModifier_clickEvent", nms.getConstructor("ChatClickable").newInstance(nms.getMethod("EnumClickAction_a").invoke(null, component.getClickAction().toString().toLowerCase()), component.getClickValue().toString()));
|
||||
}
|
||||
if (component.getColor() != null) nms.setField(modifier, "ChatModifier_color", Enum.valueOf((Class<Enum>) nms.getClass("EnumChatFormat"), component.getColor().getLegacyColor().toString()));
|
||||
nms.setField(modifier, "ChatModifier_bold", component.getBold());
|
||||
nms.setField(modifier, "ChatModifier_italic", component.getItalic());
|
||||
nms.setField(modifier, "ChatModifier_underlined", component.getUnderlined());
|
||||
nms.setField(modifier, "ChatModifier_strikethrough", component.getStrikethrough());
|
||||
nms.setField(modifier, "ChatModifier_obfuscated", component.getObfuscated());
|
||||
}
|
||||
nms.setField(chat, "ChatBaseComponent_modifier", modifier);
|
||||
for (IChatBaseComponent extra : component.getExtra()) {
|
||||
nms.getMethod("ChatComponentText_addSibling").invoke(chat, toNMSComponent0(extra, clientVersion));
|
||||
}
|
||||
return chat;
|
||||
}
|
||||
|
||||
private Object newScoreboardObjective(String objectiveName) throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return nms.getConstructor("ScoreboardObjective").newInstance(null, objectiveName, null, stringToComponent("{\"text\":\"\"}"), null);
|
||||
}
|
||||
return nms.getConstructor("ScoreboardObjective").newInstance(null, objectiveName, nms.getFields(nms.getClass("IScoreboardCriteria"), nms.getClass("IScoreboardCriteria")).get(0).get(null));
|
||||
}
|
||||
}
|
||||
+169
-133
@@ -1,144 +1,180 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
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.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.features.PipelineInjector;
|
||||
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;
|
||||
|
||||
public class BukkitPipelineInjector extends PipelineInjector {
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
//handler to inject before
|
||||
private static final String INJECT_POSITION = "packet_handler";
|
||||
|
||||
//nms storage
|
||||
private NMSStorage nms;
|
||||
/**
|
||||
* Pipeline injection for bukkit
|
||||
*/
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param tab - tab instance
|
||||
* @param nms - nms storage
|
||||
*/
|
||||
public BukkitPipelineInjector(TAB tab, NMSStorage nms){
|
||||
super(tab);
|
||||
this.nms = nms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(TabPlayer player) {
|
||||
if (!player.getChannel().pipeline().names().contains(INJECT_POSITION)) {
|
||||
//fake player or waterfall bug
|
||||
return;
|
||||
}
|
||||
uninject(player);
|
||||
try {
|
||||
player.getChannel().pipeline().addBefore(INJECT_POSITION, DECODER_NAME, new BukkitChannelDuplexHandler(player));
|
||||
} catch (NoSuchElementException | IllegalArgumentException e) {
|
||||
//idk how does this keep happening but whatever
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BukkitPipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninject(TabPlayer player) {
|
||||
try {
|
||||
if (player.getChannel().pipeline().names().contains(DECODER_NAME)) player.getChannel().pipeline().remove(DECODER_NAME);
|
||||
} catch (NoSuchElementException e) {
|
||||
//for whatever reason this rarely throws
|
||||
//java.util.NoSuchElementException: TABReader
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom channel duplex handler override
|
||||
*/
|
||||
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
|
||||
|
||||
//injected player
|
||||
private TabPlayer player;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player
|
||||
* @param player - player to inject
|
||||
*/
|
||||
public BukkitChannelDuplexHandler(TabPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
@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
|
||||
public void channelRead(ChannelHandlerContext context, Object packet) {
|
||||
try {
|
||||
Object modifiedPacket = tab.getFeatureManager().onPacketReceive(player, packet);
|
||||
if (modifiedPacket != null) super.channelRead(context, modifiedPacket);
|
||||
} catch (Exception e){
|
||||
tab.getErrorManager().printError("An error occurred when reading packets", e);
|
||||
}
|
||||
}
|
||||
@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
|
||||
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
|
||||
try {
|
||||
if (nms.getClass("PacketPlayOutPlayerInfo").isInstance(packet)) {
|
||||
super.write(context, tab.getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
|
||||
return;
|
||||
}
|
||||
if (tab.getFeatureManager().getNameTagFeature() != null && antiOverrideTeams && nms.getClass("PacketPlayOutScoreboardTeam").isInstance(packet)) {
|
||||
modifyPlayers(packet);
|
||||
super.write(context, packet, channelPromise);
|
||||
return;
|
||||
}
|
||||
if (nms.getClass("PacketPlayOutScoreboardDisplayObjective").isInstance(packet) && antiOverrideObjectives && tab.getFeatureManager().onDisplayObjective(player, packet)) {
|
||||
return;
|
||||
}
|
||||
if (antiOverrideObjectives && nms.getClass("PacketPlayOutScoreboardObjective").isInstance(packet)) {
|
||||
tab.getFeatureManager().onObjective(player, packet);
|
||||
}
|
||||
tab.getFeatureManager().onPacketSend(player, packet);
|
||||
} catch (Throwable e){
|
||||
tab.getErrorManager().printError("An error occurred when reading packets", e);
|
||||
}
|
||||
try {
|
||||
super.write(context, packet, channelPromise);
|
||||
} catch (Throwable e) {
|
||||
tab.getErrorManager().printError("Failed to forward packet " + packet.getClass().getSimpleName() + " to " + player.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all real players from team if packet does not come from TAB and reports this to override log
|
||||
* @param packetPlayOutScoreboardTeam - team packet
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws IllegalAccessException {
|
||||
long time = System.nanoTime();
|
||||
Collection<String> players = (Collection<String>) nms.getField("PacketPlayOutScoreboardTeam_PLAYERS").get(packetPlayOutScoreboardTeam);
|
||||
String teamName = (String) nms.getField("PacketPlayOutScoreboardTeam_NAME").get(packetPlayOutScoreboardTeam);
|
||||
if (players == null) return;
|
||||
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
|
||||
Collection<String> newList = new ArrayList<>();
|
||||
for (String entry : players) {
|
||||
TabPlayer p = tab.getPlayer(entry);
|
||||
if (p == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
if (!tab.getFeatureManager().getNameTagFeature().getPlayersInDisabledWorlds().contains(p) && !p.hasTeamHandlingPaused() &&
|
||||
!teamName.equals(p.getTeamName())) {
|
||||
logTeamOverride(teamName, entry);
|
||||
} else {
|
||||
newList.add(entry);
|
||||
}
|
||||
}
|
||||
nms.setField(packetPlayOutScoreboardTeam, "PacketPlayOutScoreboardTeam_PLAYERS", newList);
|
||||
tab.getCPUManager().addTime(TabFeature.NAMETAGS, UsageType.ANTI_OVERRIDE, System.nanoTime()-time);
|
||||
}
|
||||
}
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManager;
|
||||
import me.neznamy.tab.shared.placeholders.Placeholder;
|
||||
import me.neznamy.tab.shared.placeholders.PlaceholderRegistry;
|
||||
import me.neznamy.tab.shared.placeholders.PlayerPlaceholder;
|
||||
import me.neznamy.tab.shared.placeholders.ServerPlaceholder;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
|
||||
/**
|
||||
* Bukkit registry to register bukkit-only placeholders
|
||||
*/
|
||||
public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
|
||||
|
||||
//formatter for 2 decimal places
|
||||
public final DecimalFormat decimal2 = ((DecimalFormat)NumberFormat.getNumberInstance(Locale.US));
|
||||
|
||||
//plugin instance
|
||||
private JavaPlugin plugin;
|
||||
|
||||
//vault economy
|
||||
private Economy economy;
|
||||
|
||||
//vault chat
|
||||
private Chat chat;
|
||||
|
||||
//placeholder registry buffer
|
||||
private List<Placeholder> placeholders;
|
||||
|
||||
private Object essentials;
|
||||
|
||||
//essentials methods
|
||||
private Method essGetUser;
|
||||
private Method essGetNickname;
|
||||
private Method essIsAfk;
|
||||
|
||||
private Method purpurIsAfk;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
* @param plugin - plugin instance
|
||||
*/
|
||||
public BukkitPlaceholderRegistry(JavaPlugin plugin) {
|
||||
decimal2.applyPattern("#.##");
|
||||
this.plugin = plugin;
|
||||
essentials = Bukkit.getPluginManager().getPlugin("Essentials");
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<Economy> rspEconomy = Bukkit.getServicesManager().getRegistration(Economy.class);
|
||||
if (rspEconomy != null) economy = rspEconomy.getProvider();
|
||||
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
|
||||
if (rspChat != null) chat = rspChat.getProvider();
|
||||
}
|
||||
if (essentials != null) {
|
||||
try {
|
||||
essGetUser = Class.forName("com.earth2me.essentials.Essentials").getMethod("getUser", Player.class);
|
||||
essGetNickname = Class.forName("com.earth2me.essentials.User").getMethod("getNickname");
|
||||
essIsAfk = Class.forName("com.earth2me.essentials.User").getMethod("isAfk");
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to load essentials methods", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Placeholder> registerPlaceholders() {
|
||||
placeholders = new ArrayList<>();
|
||||
placeholders.add(new PlayerPlaceholder("%money%", 1000) {
|
||||
public String get(TabPlayer p) {
|
||||
if (economy != null) return decimal2.format(economy.getBalance((Player) p.getPlayer()));
|
||||
return "-";
|
||||
}
|
||||
});
|
||||
|
||||
placeholders.add(new PlayerPlaceholder("%displayname%", 500) {
|
||||
public String get(TabPlayer p) {
|
||||
return ((Player) p.getPlayer()).getDisplayName();
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%deaths%", 1000) {
|
||||
public String get(TabPlayer p) {
|
||||
return String.valueOf(((Player) p.getPlayer()).getStatistic(Statistic.DEATHS));
|
||||
}
|
||||
});
|
||||
placeholders.add(new ServerPlaceholder("%tps%", 1000) {
|
||||
public String get() {
|
||||
try {
|
||||
Object nmsServer = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
double value = ((double[]) nmsServer.getClass().getField("recentTps").get(nmsServer))[0];
|
||||
return decimal2.format(Math.min(20, value));
|
||||
} catch (Exception t) {
|
||||
return "-1";
|
||||
}
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%canseeonline%", 2000) {
|
||||
public String get(TabPlayer p) {
|
||||
int count = 0;
|
||||
for (TabPlayer all : TAB.getInstance().getPlayers()){
|
||||
if (((Player) p.getPlayer()).canSee((Player) all.getPlayer())) count++;
|
||||
}
|
||||
return String.valueOf(count);
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%canseestaffonline%", 2000) {
|
||||
public String get(TabPlayer p) {
|
||||
int count = 0;
|
||||
for (TabPlayer all : TAB.getInstance().getPlayers()){
|
||||
if (all.isStaff() && ((Player) p.getPlayer()).canSee((Player) all.getPlayer())) count++;
|
||||
}
|
||||
return String.valueOf(count);
|
||||
}
|
||||
});
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 6) {
|
||||
placeholders.add(new PlayerPlaceholder("%health%", 100) {
|
||||
public String get(TabPlayer p) {
|
||||
return String.valueOf((int) Math.ceil(((Player) p.getPlayer()).getHealth()));
|
||||
}
|
||||
});
|
||||
}
|
||||
registerAFKPlaceholder();
|
||||
registerVaultPlaceholders();
|
||||
registerPositionPlaceholders();
|
||||
registerEssentialsPlaceholders();
|
||||
registerSyncPlaceholders();
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers AFK placeholder
|
||||
*/
|
||||
private void registerAFKPlaceholder() {
|
||||
Plugin afkplus = Bukkit.getPluginManager().getPlugin("AFKPlus");
|
||||
boolean antiafkplus = Bukkit.getPluginManager().isPluginEnabled("AntiAFKPlus");
|
||||
try {
|
||||
purpurIsAfk = Player.class.getMethod("isAfk");
|
||||
} catch (NoSuchMethodException | SecurityException e1) {
|
||||
//not purpur
|
||||
}
|
||||
String noAfk = TAB.getInstance().getConfiguration().getConfig().getString("placeholders.afk-no", "");
|
||||
String yesAfk = TAB.getInstance().getConfiguration().getConfig().getString("placeholders.afk-yes", " &4*&4&lAFK&4*&r");
|
||||
placeholders.add(new PlayerPlaceholder("%afk%", 500) {
|
||||
public String get(TabPlayer p) {
|
||||
try {
|
||||
if (essentials != null) {
|
||||
Object user = essGetUser.invoke(Bukkit.getPluginManager().getPlugin("Essentials"), p.getPlayer());
|
||||
if ((boolean) essIsAfk.invoke(user)) return yesAfk;
|
||||
}
|
||||
if (afkplus != null) {
|
||||
Object afkplusplayer = afkplus.getClass().getMethod("getPlayer", UUID.class).invoke(afkplus, p.getUniqueId());
|
||||
if ((boolean) afkplusplayer.getClass().getMethod("isAFK").invoke(afkplusplayer)) return yesAfk;
|
||||
}
|
||||
if (antiafkplus) {
|
||||
Object api = Class.forName("de.kinglol12345.AntiAFKPlus.api.AntiAFKPlusAPI").getDeclaredMethod("getAPI").invoke(null);
|
||||
if ((boolean) api.getClass().getMethod("isAFK", Player.class).invoke(api, p.getPlayer())) return yesAfk;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError("", "Failed to check AFK status of " + p.getName(), e);
|
||||
}
|
||||
try {
|
||||
if (purpurIsAfk != null && (boolean) purpurIsAfk.invoke(p.getPlayer())) return yesAfk;
|
||||
} catch (Exception e) {
|
||||
//not purpur
|
||||
}
|
||||
return noAfk;
|
||||
}
|
||||
@Override
|
||||
public String[] getNestedStrings(){
|
||||
return new String[] {yesAfk, noAfk};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers vault placeholders
|
||||
*/
|
||||
private void registerVaultPlaceholders() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault") && chat != null) {
|
||||
placeholders.add(new PlayerPlaceholder("%vault-prefix%", 500) {
|
||||
|
||||
public String get(TabPlayer p) {
|
||||
return chat.getPlayerPrefix((Player) p.getPlayer());
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%vault-suffix%", 500) {
|
||||
|
||||
public String get(TabPlayer p) {
|
||||
return chat.getPlayerSuffix((Player) p.getPlayer());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
placeholders.add(new ServerPlaceholder("%vault-prefix%", 1000000) {
|
||||
public String get() {
|
||||
return "";
|
||||
}
|
||||
});
|
||||
placeholders.add(new ServerPlaceholder("%vault-suffix%", 1000000) {
|
||||
public String get() {
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers position placeholders
|
||||
*/
|
||||
private void registerPositionPlaceholders() {
|
||||
placeholders.add(new PlayerPlaceholder("%xPos%", 100) {
|
||||
public String get(TabPlayer p) {
|
||||
return String.valueOf(((Player) p.getPlayer()).getLocation().getBlockX());
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%yPos%", 100) {
|
||||
public String get(TabPlayer p) {
|
||||
return String.valueOf(((Player) p.getPlayer()).getLocation().getBlockY());
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%zPos%", 100) {
|
||||
public String get(TabPlayer p) {
|
||||
return String.valueOf(((Player) p.getPlayer()).getLocation().getBlockZ());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers essentials placeholders
|
||||
*/
|
||||
private void registerEssentialsPlaceholders() {
|
||||
if (essentials != null) {
|
||||
placeholders.add(new PlayerPlaceholder("%essentialsnick%", 1000) {
|
||||
public String get(TabPlayer p) {
|
||||
if (essGetNickname == null) return "<Internal plugin error>";
|
||||
String name = null;
|
||||
try {
|
||||
name = (String) essGetNickname.invoke(essGetUser.invoke(essentials, p.getPlayer()));
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get Essentials nickname of " + p.getName(), e);
|
||||
}
|
||||
if (name == null || name.length() == 0) return p.getName();
|
||||
return TAB.getInstance().getConfiguration().getEssentialsNickPrefix() + name;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
placeholders.add(new PlayerPlaceholder("%essentialsnick%", 100000000) {
|
||||
public String get(TabPlayer p) {
|
||||
return p.getName();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers synchronous placeholders
|
||||
*/
|
||||
private void registerSyncPlaceholders() {
|
||||
PlaceholderManager pl = TAB.getInstance().getPlaceholderManager();
|
||||
for (String identifier : pl.getAllUsedPlaceholderIdentifiers()) {
|
||||
if (identifier.startsWith("%sync:")) {
|
||||
int refresh;
|
||||
if (pl.getServerPlaceholderRefreshIntervals().containsKey(identifier)) {
|
||||
refresh = pl.getServerPlaceholderRefreshIntervals().get(identifier);
|
||||
} else if (pl.getPlayerPlaceholderRefreshIntervals().containsKey(identifier)) {
|
||||
refresh = pl.getPlayerPlaceholderRefreshIntervals().get(identifier);
|
||||
} else {
|
||||
refresh = pl.getDefaultRefresh();
|
||||
}
|
||||
placeholders.add(new PlayerPlaceholder(identifier, refresh) {
|
||||
|
||||
@Override
|
||||
public String get(TabPlayer p) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
|
||||
long time = System.nanoTime();
|
||||
String syncedPlaceholder = identifier.substring(6, identifier.length()-1);
|
||||
String value = ((BukkitPlatform) TAB.getInstance().getPlatform()).setPlaceholders((Player) p.getPlayer(), "%" + syncedPlaceholder + "%");
|
||||
getLastValues().put(p.getName(), value);
|
||||
if (!getForceUpdate().contains(p.getName())) getForceUpdate().add(p.getName());
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(getIdentifier(), System.nanoTime()-time);
|
||||
});
|
||||
return getLastValues().get(p.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,312 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.event.BukkitTABLoadEvent;
|
||||
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerlist;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PetFix;
|
||||
import me.neznamy.tab.platforms.bukkit.features.TabExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.features.unlimitedtags.NameTagX;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.permission.Vault;
|
||||
import me.neznamy.tab.shared.Platform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.BelowName;
|
||||
import me.neznamy.tab.shared.features.NameTag;
|
||||
import me.neznamy.tab.shared.features.PingSpoof;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManager;
|
||||
import me.neznamy.tab.shared.features.SpectatorFix;
|
||||
import me.neznamy.tab.shared.features.TabObjective;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManager;
|
||||
import me.neznamy.tab.shared.permission.LuckPerms;
|
||||
import me.neznamy.tab.shared.permission.None;
|
||||
import me.neznamy.tab.shared.permission.PermissionPlugin;
|
||||
import me.neznamy.tab.shared.permission.UltraPermissions;
|
||||
import me.neznamy.tab.shared.placeholders.Placeholder;
|
||||
import me.neznamy.tab.shared.placeholders.PlayerPlaceholder;
|
||||
import me.neznamy.tab.shared.placeholders.RelationalPlaceholder;
|
||||
import me.neznamy.tab.shared.placeholders.ServerPlaceholder;
|
||||
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
|
||||
/**
|
||||
* Bukkit implementation of Platform
|
||||
*/
|
||||
public class BukkitPlatform implements Platform {
|
||||
|
||||
//plugin instance
|
||||
private JavaPlugin plugin;
|
||||
|
||||
//nms storage
|
||||
private NMSStorage nms;
|
||||
|
||||
//booleans to check plugin presence
|
||||
private Plugin placeholderAPI;
|
||||
private boolean viaversion;
|
||||
private boolean idisguise;
|
||||
private boolean libsdisguises;
|
||||
private boolean essentials;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param plugin - plugin instance
|
||||
* @param nms - nms storage
|
||||
*/
|
||||
public BukkitPlatform(JavaPlugin plugin, NMSStorage nms) {
|
||||
this.plugin = plugin;
|
||||
this.nms = nms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionPlugin detectPermissionPlugin() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
|
||||
return new LuckPerms(Bukkit.getPluginManager().getPlugin("LuckPerms").getDescription().getVersion());
|
||||
} else if (Bukkit.getPluginManager().isPluginEnabled("UltraPermissions")) {
|
||||
return new UltraPermissions(Bukkit.getPluginManager().getPlugin("UltraPermissions").getDescription().getVersion());
|
||||
} else if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
return new Vault(Bukkit.getServicesManager().getRegistration(Permission.class).getProvider(), Bukkit.getPluginManager().getPlugin("Vault").getDescription().getVersion());
|
||||
} else {
|
||||
return new None();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFeatures() {
|
||||
placeholderAPI = Bukkit.getPluginManager().getPlugin("PlaceholderAPI");
|
||||
viaversion = Bukkit.getPluginManager().isPluginEnabled("ViaVersion");
|
||||
idisguise = Bukkit.getPluginManager().isPluginEnabled("iDisguise");
|
||||
libsdisguises = Bukkit.getPluginManager().isPluginEnabled("LibsDisguises");
|
||||
essentials = Bukkit.getPluginManager().isPluginEnabled("Essentials");
|
||||
TAB tab = TAB.getInstance();
|
||||
tab.getPlaceholderManager().addRegistry(new BukkitPlaceholderRegistry(plugin));
|
||||
tab.getPlaceholderManager().addRegistry(new UniversalPlaceholderRegistry());
|
||||
tab.getPlaceholderManager().registerPlaceholders();
|
||||
if (nms.getMinorVersion() >= 8 && tab.getConfiguration().isPipelineInjection()) {
|
||||
tab.getFeatureManager().registerFeature("injection", new BukkitPipelineInjector(tab, nms));
|
||||
}
|
||||
loadNametagFeature(tab);
|
||||
loadUniversalFeatures();
|
||||
if (tab.getConfiguration().getConfig().getBoolean("ping-spoof.enabled", false)) tab.getFeatureManager().registerFeature("pingspoof", new PingSpoof());
|
||||
if (tab.getConfiguration().getConfig().getString("yellow-number-in-tablist", "%ping%").length() > 0) tab.getFeatureManager().registerFeature("tabobjective", new TabObjective(tab));
|
||||
if (tab.getConfiguration().getConfig().getBoolean("do-not-move-spectators", false)) tab.getFeatureManager().registerFeature("spectatorfix", new SpectatorFix());
|
||||
if (tab.getConfiguration().getConfig().getBoolean("classic-vanilla-belowname.enabled", true)) tab.getFeatureManager().registerFeature("belowname", new BelowName(tab));
|
||||
if (tab.getConfiguration().getPremiumConfig() != null && tab.getConfiguration().getPremiumConfig().getBoolean("scoreboard.enabled", false)) tab.getFeatureManager().registerFeature("scoreboard", new ScoreboardManager(tab));
|
||||
if (tab.getConfiguration().getBossbarConfig().getBoolean("bossbar-enabled", false) && nms.getMinorVersion() < 9) tab.getFeatureManager().registerFeature("bossbar1.8", new WitherBossBar(tab, plugin));
|
||||
if (nms.getMinorVersion() >= 9 && tab.getConfiguration().getConfig().getBoolean("fix-pet-names", false)) tab.getFeatureManager().registerFeature("petfix", new PetFix(nms));
|
||||
if (tab.getConfiguration().getConfig().getBoolean("per-world-playerlist.enabled", false)) tab.getFeatureManager().registerFeature("pwp", new PerWorldPlayerlist(plugin, tab));
|
||||
if (placeholderAPI != null) {
|
||||
new TabExpansion(plugin);
|
||||
}
|
||||
for (Player p : getOnlinePlayers()) {
|
||||
tab.addPlayer(new BukkitTabPlayer(p, Main.getProtocolVersion(p)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads nametag feature from config
|
||||
* @param tab - tab instance
|
||||
*/
|
||||
private void loadNametagFeature(TAB tab) {
|
||||
if (tab.getConfiguration().getConfig().getBoolean("change-nametag-prefix-suffix", true)) {
|
||||
if (tab.getConfiguration().getConfig().getBoolean("unlimited-nametag-prefix-suffix-mode.enabled", false) && nms.getMinorVersion() >= 8) {
|
||||
tab.getFeatureManager().registerFeature("nametagx", new NameTagX(plugin, nms, tab));
|
||||
} else {
|
||||
tab.getFeatureManager().registerFeature("nametag16", new NameTag(tab));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of online players from Bukkit API
|
||||
* @return list of online players from Bukkit API
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Player[] getOnlinePlayers() {
|
||||
try {
|
||||
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
|
||||
if (players instanceof Player[]) {
|
||||
//1.7.x
|
||||
return (Player[]) players;
|
||||
} else {
|
||||
//1.8+
|
||||
return ((Collection<Player>)players).toArray(new Player[0]);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get online players", e);
|
||||
return new Player[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendConsoleMessage(String message, boolean translateColors) {
|
||||
Bukkit.getConsoleSender().sendMessage(translateColors ? message.replace('&', '\u00a7') : message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(String identifier) {
|
||||
if (identifier.contains("_")) {
|
||||
PlaceholderManager pl = TAB.getInstance().getPlaceholderManager();
|
||||
if (identifier.startsWith("%rel_")) {
|
||||
//relational placeholder
|
||||
registerRelationalPlaceholder(identifier, pl.getRelationalRefresh(identifier));
|
||||
} else {
|
||||
//normal placeholder
|
||||
String expansion = identifier.split("_")[0].substring(1).toLowerCase();
|
||||
if (expansion.equals("some")) return;
|
||||
if (pl.getServerPlaceholderRefreshIntervals().containsKey(identifier)) {
|
||||
registerServerPlaceholder(identifier, pl.getServerPlaceholderRefreshIntervals().get(identifier));
|
||||
return;
|
||||
}
|
||||
if (pl.getPlayerPlaceholderRefreshIntervals().containsKey(identifier)) {
|
||||
registerPlayerPlaceholder(identifier, pl.getPlayerPlaceholderRefreshIntervals().get(identifier));
|
||||
return;
|
||||
}
|
||||
registerPlayerPlaceholder(identifier, pl.getDefaultRefresh());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers server placeholder
|
||||
* @param identifier - placeholder identifier
|
||||
* @param refresh - refresh interval in milliseconds
|
||||
*/
|
||||
private void registerServerPlaceholder(String identifier, int refresh) {
|
||||
BukkitPlatform pl = this;
|
||||
TAB.getInstance().getPlaceholderManager().registerPlaceholder(new ServerPlaceholder(identifier, TAB.getInstance().getErrorManager().fixPlaceholderInterval(identifier, refresh)){
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
return pl.setPlaceholders(null, identifier);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers player placeholder
|
||||
* @param identifier - placeholder identifier
|
||||
* @param refresh - refresh interval in milliseconds
|
||||
*/
|
||||
private void registerPlayerPlaceholder(String identifier, int refresh) {
|
||||
BukkitPlatform pl = this;
|
||||
TAB.getInstance().getPlaceholderManager().registerPlaceholder(new PlayerPlaceholder(identifier, TAB.getInstance().getErrorManager().fixPlaceholderInterval(identifier, refresh)) {
|
||||
|
||||
@Override
|
||||
public String get(TabPlayer p) {
|
||||
return pl.setPlaceholders((Player) p.getPlayer(), identifier);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers relational placeholder
|
||||
* @param identifier - placeholder identifier
|
||||
* @param refresh - refresh interval in milliseconds
|
||||
*/
|
||||
private void registerRelationalPlaceholder(String identifier, int refresh) {
|
||||
TAB.getInstance().getPlaceholderManager().registerPlaceholder(new RelationalPlaceholder(identifier, TAB.getInstance().getErrorManager().fixPlaceholderInterval(identifier, refresh)) {
|
||||
|
||||
@Override
|
||||
public String get(TabPlayer viewer, TabPlayer target) {
|
||||
if (placeholderAPI == null) return identifier;
|
||||
try {
|
||||
return PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier);
|
||||
} catch (Exception t) {
|
||||
TAB.getInstance().getErrorManager().printError("PlaceholderAPI v" + placeholderAPI.getDescription().getVersion() + " generated an error when setting relational placeholder " + identifier + " for viewer " + viewer.getName() + " and target " + target.getName(), t, false, TAB.getInstance().getErrorManager().getPapiErrorLog());
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs PlaceholderAPI call and returns the output. If the task fails, error is logged and "ERROR" returned instead
|
||||
* @param player - player to set placeholder for
|
||||
* @param placeholder - placeholder
|
||||
* @return result from PlaceholderAPI
|
||||
*/
|
||||
public String setPlaceholders(Player player, String placeholder) {
|
||||
if (placeholderAPI == null) return placeholder;
|
||||
try {
|
||||
return PlaceholderAPI.setPlaceholders(player, placeholder);
|
||||
} catch (Exception t) {
|
||||
String playername = (player == null ? "<null>" : player.getName());
|
||||
TAB.getInstance().getErrorManager().printError("PlaceholderAPI v" + placeholderAPI.getDescription().getVersion() + " generated an error when setting placeholder " + placeholder + " for player " + playername, t, false, TAB.getInstance().getErrorManager().getPapiErrorLog());
|
||||
return "ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerVersion() {
|
||||
return Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSeparatorType() {
|
||||
return "world";
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String replaceAllPlaceholders(String string, TabPlayer sender) {
|
||||
if (string == null) return null;
|
||||
String replaced = string;
|
||||
for (Placeholder p : TAB.getInstance().getPlaceholderManager().getAllPlaceholders()) {
|
||||
if (replaced.contains(p.getIdentifier())) {
|
||||
if (p instanceof ServerPlaceholder) {
|
||||
((ServerPlaceholder)p).update();
|
||||
}
|
||||
if (p instanceof PlayerPlaceholder) {
|
||||
((PlayerPlaceholder)p).update(sender);
|
||||
}
|
||||
replaced = p.set(replaced, sender);
|
||||
}
|
||||
}
|
||||
replaced = setPlaceholders(sender == null ? null : (Player) sender.getPlayer(), replaced);
|
||||
return replaced;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callLoadEvent() {
|
||||
Bukkit.getPluginManager().callEvent(new BukkitTABLoadEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxPlayers() {
|
||||
return Bukkit.getMaxPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigName() {
|
||||
return "bukkitconfig.yml";
|
||||
}
|
||||
|
||||
public boolean isViaversionEnabled() {
|
||||
return viaversion;
|
||||
}
|
||||
|
||||
public boolean isLibsdisguisesEnabled() {
|
||||
return libsdisguises;
|
||||
}
|
||||
|
||||
public boolean isIdisguiseEnabled() {
|
||||
return idisguise;
|
||||
}
|
||||
|
||||
public boolean isEssentialsEnabled() {
|
||||
return essentials;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,219 +1,162 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.boss.BarColor;
|
||||
import org.bukkit.boss.BarFlag;
|
||||
import org.bukkit.boss.BarStyle;
|
||||
import org.bukkit.boss.BossBar;
|
||||
import org.bukkit.entity.Entity;
|
||||
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 com.viaversion.viaversion.api.Via;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.ITabPlayer;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutBoss;
|
||||
import me.neznamy.tab.shared.rgb.RGBUtils;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* TabPlayer for Bukkit
|
||||
* TabPlayer implementation for Bukkit platform
|
||||
*/
|
||||
public class BukkitTabPlayer extends ITabPlayer {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Getter
|
||||
public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
//bukkit player
|
||||
private Player player;
|
||||
/** Player's NMS handle (EntityPlayer), preloading for speed */
|
||||
@NotNull
|
||||
private final Object handle;
|
||||
|
||||
//nms handle
|
||||
private Object handle;
|
||||
/** Player's connection for sending packets, preloading for speed */
|
||||
@NotNull
|
||||
private final Object playerConnection;
|
||||
|
||||
//nms player connection
|
||||
private Object playerConnection;
|
||||
|
||||
//player's visible boss bars
|
||||
private Map<UUID, BossBar> bossbars = new HashMap<>();
|
||||
@NotNull
|
||||
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
* @param p - bukkit player
|
||||
*/
|
||||
public BukkitTabPlayer(Player p, int protocolVersion){
|
||||
player = p;
|
||||
world = p.getWorld().getName();
|
||||
try {
|
||||
handle = NMSStorage.getInstance().getMethod("getHandle").invoke(player);
|
||||
playerConnection = NMSStorage.getInstance().getField("PLAYER_CONNECTION").get(handle);
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get playerConnection of " + p.getName(), e);
|
||||
}
|
||||
try {
|
||||
if (NMSStorage.getInstance().getField("CHANNEL") != null)
|
||||
channel = (Channel) NMSStorage.getInstance().getField("CHANNEL").get(NMSStorage.getInstance().getField("NETWORK_MANAGER").get(playerConnection));
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + p.getName(), e);
|
||||
}
|
||||
uniqueId = p.getUniqueId();
|
||||
name = p.getName();
|
||||
version = ProtocolVersion.fromNetworkId(protocolVersion);
|
||||
init();
|
||||
}
|
||||
@NotNull
|
||||
private final TabList tabList = new BukkitTabList(this);
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
return player.hasPermission(permission);
|
||||
}
|
||||
@NotNull
|
||||
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
|
||||
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
|
||||
|
||||
@Override
|
||||
public long getPing() {
|
||||
try {
|
||||
int ping = NMSStorage.getInstance().getField("PING").getInt(handle);
|
||||
if (ping > 10000 || ping < 0) ping = -1;
|
||||
return ping;
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@NotNull
|
||||
private final EntityView entityView = new PacketEntityView(this);
|
||||
|
||||
@Override
|
||||
public void sendPacket(Object nmsPacket) {
|
||||
if (nmsPacket == null || !player.isOnline()) return;
|
||||
try {
|
||||
if (((BukkitPlatform)TAB.getInstance().getPlatform()).isViaversionEnabled() && nmsPacket instanceof ByteBuf) {
|
||||
Via.getAPI().sendRawPacket(uniqueId, (ByteBuf) nmsPacket);
|
||||
return;
|
||||
}
|
||||
if (nmsPacket instanceof PacketPlayOutBoss) {
|
||||
handle((PacketPlayOutBoss) nmsPacket);
|
||||
return;
|
||||
}
|
||||
NMSStorage.getInstance().getMethod("sendPacket").invoke(playerConnection, nmsPacket);
|
||||
} catch (IllegalArgumentException e) {
|
||||
//java.lang.IllegalArgumentException: This player is not controlled by ViaVersion!
|
||||
//this is only used to send 1.9 bossbar packets on 1.8 servers, no idea why it does this sometimes
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(PacketPlayOutBoss packet) {
|
||||
Set<BarFlag> flags = new HashSet<>();
|
||||
BossBar bar;
|
||||
switch (packet.getOperation()) {
|
||||
case ADD:
|
||||
if (packet.isCreateWorldFog()) flags.add(BarFlag.CREATE_FOG);
|
||||
if (packet.isDarkenScreen()) flags.add(BarFlag.DARKEN_SKY);
|
||||
if (packet.isPlayMusic()) flags.add(BarFlag.PLAY_BOSS_MUSIC);
|
||||
bar = Bukkit.createBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && NMSStorage.getInstance().getMinorVersion() >= 16),
|
||||
BarColor.valueOf(packet.getColor().name()),
|
||||
BarStyle.valueOf(packet.getOverlay().name().replace("PROGRESS", "SOLID").replace("NOTCHED", "SEGMENTED")),
|
||||
flags.toArray(new BarFlag[0]));
|
||||
bar.setProgress(packet.getPct());
|
||||
bossbars.put(packet.getId(), bar);
|
||||
bar.addPlayer(player);
|
||||
break;
|
||||
case REMOVE:
|
||||
bossbars.get(packet.getId()).removePlayer(player);
|
||||
bossbars.remove(packet.getId());
|
||||
break;
|
||||
case UPDATE_PCT:
|
||||
bossbars.get(packet.getId()).setProgress(packet.getPct());
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
bossbars.get(packet.getId()).setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && NMSStorage.getInstance().getMinorVersion() >= 16));
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
bossbars.get(packet.getId()).setColor(BarColor.valueOf(packet.getColor().name()));
|
||||
bossbars.get(packet.getId()).setStyle(BarStyle.valueOf(packet.getOverlay().name().replace("PROGRESS", "SOLID").replace("NOTCHED", "SEGMENTED")));
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
bar = bossbars.get(packet.getId());
|
||||
processFlag(bar, packet.isCreateWorldFog(), BarFlag.CREATE_FOG);
|
||||
processFlag(bar, packet.isDarkenScreen(), BarFlag.DARKEN_SKY);
|
||||
processFlag(bar, packet.isPlayMusic(), BarFlag.PLAY_BOSS_MUSIC);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
|
||||
if (targetValue) {
|
||||
if (!bar.hasFlag(flag)) {
|
||||
bar.addFlag(flag);
|
||||
}
|
||||
} else {
|
||||
if (bar.hasFlag(flag)) {
|
||||
bar.removeFlag(flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 hasInvisibilityPotion() {
|
||||
return player.hasPotionEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String permission) {
|
||||
return getPlayer().hasPermission(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
try {
|
||||
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).isLibsdisguisesEnabled()) return false;
|
||||
return (boolean) Class.forName("me.libraryaddict.disguise.DisguiseAPI").getMethod("isDisguised", Entity.class).invoke(null, player);
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError(false, "Failed to check disguise status using LibsDisguises", e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getPing() {
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
return getPlayer().getPing();
|
||||
}
|
||||
return NMSStorage.getInstance().PING.getInt(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSkin() {
|
||||
try {
|
||||
return Class.forName("com.mojang.authlib.GameProfile").getMethod("getProperties").invoke(NMSStorage.getInstance().getMethod("getProfile").invoke(handle));
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to get skin of " + getName(), e);
|
||||
}
|
||||
}
|
||||
@SneakyThrows
|
||||
public void sendPacket(@NotNull Object nmsPacket) {
|
||||
if (!getPlayer().isOnline()) return;
|
||||
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return player.isOnline();
|
||||
}
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return getPlayer().hasPotionEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
try {
|
||||
if (((BukkitPlatform)TAB.getInstance().getPlatform()).isEssentialsEnabled()) {
|
||||
Object essentials = Bukkit.getPluginManager().getPlugin("Essentials");
|
||||
Object user = essentials.getClass().getMethod("getUser", Player.class).invoke(essentials, player);
|
||||
boolean vanished = (boolean) user.getClass().getMethod("isVanished").invoke(user);
|
||||
if (vanished) return true;
|
||||
}
|
||||
List<MetadataValue> metadata = player.getMetadata("vanished");
|
||||
return !metadata.isEmpty() && metadata.get(0).asBoolean();
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to check vanish status of " + player.getName(), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
return LibsDisguisesHook.isDisguised(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return player.getGameMode().getValue();
|
||||
}
|
||||
}
|
||||
@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();
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
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.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
|
||||
/**
|
||||
* Main class for Bukkit platform
|
||||
*/
|
||||
public class Main extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable(){
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a77[TAB] Server version: " + Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")");
|
||||
if (!isVersionSupported()){
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
TAB.setInstance(new TAB(new BukkitPlatform(this, NMSStorage.getInstance()), new BukkitPacketBuilder(NMSStorage.getInstance()), ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0])));
|
||||
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN) {
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a7c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly.");
|
||||
}
|
||||
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), this);
|
||||
TABCommand command = new TABCommand();
|
||||
Bukkit.getPluginCommand("tab").setExecutor(command);
|
||||
Bukkit.getPluginCommand("tab").setTabCompleter(command);
|
||||
TAB.getInstance().load();
|
||||
Metrics metrics = new Metrics(this, 5304);
|
||||
metrics.addCustomChart(new SimplePie("unlimited_nametag_mode_enabled", () -> TAB.getInstance().getFeatureManager().isFeatureEnabled("nametagx") ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie("placeholderapi", () -> Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI") ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie("permission_system", () -> TAB.getInstance().getPermissionPlugin().getName()));
|
||||
metrics.addCustomChart(new SimplePie("server_version", () -> "1." + NMSStorage.getInstance().getMinorVersion() + ".x"));
|
||||
metrics.addCustomChart(new SimplePie("using_premium_version", () -> TAB.getInstance().isPremium() ? "Yes" : "No"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
//null check due to L23 return making L30 not run
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all used NMS classes, constructors, fields and methods and returns true if everything went successfully and version is marked as compatible
|
||||
* @return true if compatible, false if not
|
||||
*/
|
||||
private boolean isVersionSupported(){
|
||||
List<String> supportedVersions = Arrays.asList(
|
||||
"v1_5_R1", "v1_5_R2", "v1_5_R3", "v1_6_R1", "v1_6_R2", "v1_6_R3",
|
||||
"v1_7_R1", "v1_7_R2", "v1_7_R3", "v1_7_R4", "v1_8_R1", "v1_8_R2", "v1_8_R3",
|
||||
"v1_9_R1", "v1_9_R2", "v1_10_R1", "v1_11_R1", "v1_12_R1", "v1_13_R1", "v1_13_R2",
|
||||
"v1_14_R1", "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1");
|
||||
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
try {
|
||||
NMSStorage.setInstance(new NMSStorage());
|
||||
if (supportedVersions.contains(serverPackage)) {
|
||||
return true;
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a7c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server version. This jar has only been tested on 1.5.x - 1.17. Disabling just to stay safe.");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (supportedVersions.contains(serverPackage)) {
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a7c[TAB] Your server version is marked as compatible, but a compatibility issue was found. Please report the error below (include your server version & fork too)");
|
||||
Bukkit.getConsoleSender().sendMessage(ex.getClass().getName() + ": " + ex.getMessage());
|
||||
for (StackTraceElement e : ex.getStackTrace()) {
|
||||
Bukkit.getConsoleSender().sendMessage("\t" + e.toString());
|
||||
}
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a7c[TAB] Your server version is completely unsupported. This plugin version only supports 1.5.x - 1.17. Disabling.");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets protocol version and returns it
|
||||
* @return protocol version of this player
|
||||
*/
|
||||
public static int getProtocolVersion(Player player) {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport")){
|
||||
int version = getProtocolVersionPS(player);
|
||||
//some PS versions return -1 on unsupported server versions instead of throwing exception
|
||||
if (version != -1 && version < TAB.getInstance().getServerVersion().getNetworkId()) return version;
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) {
|
||||
return getProtocolVersionVia(player);
|
||||
}
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns protocol version of this player using ProtocolSupport
|
||||
* @return protocol version of this player using ProtocolSupport
|
||||
*/
|
||||
private static int getProtocolVersionPS(Player player){
|
||||
try {
|
||||
Object protocolVersion = Class.forName("protocolsupport.api.ProtocolSupportAPI").getMethod("getProtocolVersion", Player.class).invoke(null, player);
|
||||
int version = (int) protocolVersion.getClass().getMethod("getId").invoke(protocolVersion);
|
||||
TAB.getInstance().debug("ProtocolSupport returned protocol version " + version + " for " + player.getName() + "(online=" + player.isOnline() + ")");
|
||||
return version;
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get protocol version of " + player.getName() + " using ProtocolSupport", e);
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns protocol version of this player using ViaVersion
|
||||
* @return protocol version of this player using ViaVersion
|
||||
*/
|
||||
private static int getProtocolVersionVia(Player player){
|
||||
try {
|
||||
int version = Via.getAPI().getPlayerVersion(player.getUniqueId());
|
||||
TAB.getInstance().debug("ViaVersion returned protocol version " + version + " for " + player.getName() + "(online=" + player.isOnline() + ")");
|
||||
return version;
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get protocol version of " + player.getName() + " using ViaVersion v" + Bukkit.getPluginManager().getPlugin("ViaVersion").getDescription().getVersion(), e);
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
}
|
||||
|
||||
public class TABCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (TAB.getInstance().isDisabled()) {
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission("tab.reload"), sender.hasPermission("tab.admin"))) {
|
||||
sender.sendMessage(message.replace('&', '\u00a7'));
|
||||
}
|
||||
} else {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof Player) {
|
||||
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
|
||||
if (p == null) return true; //player not loaded correctly
|
||||
}
|
||||
TAB.getInstance().getCommand().execute(p, args);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof Player) {
|
||||
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
|
||||
if (p == null) return new ArrayList<>(); //player not loaded correctly
|
||||
}
|
||||
return TAB.getInstance().getCommand().complete(p, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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";
|
||||
}
|
||||
}
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
|
||||
/**
|
||||
* Per-world-playerlist feature handler
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PerWorldPlayerlist implements Loadable, Listener {
|
||||
|
||||
//plugin instance
|
||||
private JavaPlugin plugin;
|
||||
|
||||
//config options
|
||||
private boolean allowBypass;
|
||||
private List<String> ignoredWorlds;
|
||||
private Map<String, List<String>> sharedWorlds;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
* @param plugin - plugin instance
|
||||
* @param tab - tab instance
|
||||
*/
|
||||
public PerWorldPlayerlist(JavaPlugin plugin, TAB tab) {
|
||||
this.plugin = plugin;
|
||||
allowBypass = tab.getConfiguration().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
ignoredWorlds = tab.getConfiguration().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
|
||||
sharedWorlds = tab.getConfiguration().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
for (Entry<String, List<String>> group : sharedWorlds.entrySet()) {
|
||||
if (group.getValue() == null) {
|
||||
tab.getErrorManager().startupWarn("World group \"" + group + "\" in per-world-playerlist does not contain any worlds. You can just remove the group.");
|
||||
} else if (group.getValue().size() == 1) {
|
||||
tab.getErrorManager().startupWarn("World group \"" + group + "\" in per-world-playerlist only contain a single world (\"" + group.getValue().get(0) +
|
||||
"\"), which has no effect and only makes config less readable. Delete the group entirely for a cleaner config.");
|
||||
}
|
||||
}
|
||||
tab.debug(String.format("Loaded PerWorldPlayerlist feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
Bukkit.getScheduler().runTask(plugin, () -> Bukkit.getOnlinePlayers().forEach(this::checkPlayer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload(){
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
}
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(TabFeature.PER_WORLD_PLAYERLIST, UsageType.PLAYER_JOIN_EVENT, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(TabFeature.PER_WORLD_PLAYERLIST, UsageType.WORLD_SWITCH_EVENT, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
private void checkPlayer(Player p) {
|
||||
for (Player all : Bukkit.getOnlinePlayers()){
|
||||
if (all == p) continue;
|
||||
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
|
||||
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
|
||||
if (!shouldSee(all, p) && all.canSee(p)) all.hidePlayer(p);
|
||||
if (shouldSee(all, p) && !all.canSee(p)) all.showPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSee(Player viewer, Player target) {
|
||||
if (target == viewer) return true;
|
||||
if ((allowBypass && viewer.hasPermission("tab.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);
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.features.types.event.QuitEventListener;
|
||||
import me.neznamy.tab.shared.features.types.packet.RawPacketListener;
|
||||
|
||||
/**
|
||||
* A feature to disable minecraft 1.9+ feature making tamed animals with custom names copy nametag properties of their owner
|
||||
* This is achieved by listening to entity spawn (<1.15) / entity metadata packets and removing owner field from the datawatcher list
|
||||
* Since 1.16 this results in client sending entity use packet twice, so we must cancel the 2nd one to prevent double toggle
|
||||
*/
|
||||
public class PetFix implements RawPacketListener, QuitEventListener {
|
||||
|
||||
//datawatcher position of pet owner field
|
||||
private int petOwnerPosition;
|
||||
|
||||
//logger of last interacts to prevent feature not working on 1.16
|
||||
private Map<String, Long> lastInteractFix = new HashMap<>();
|
||||
|
||||
//nms storage
|
||||
private NMSStorage nms;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
* @param nms
|
||||
*/
|
||||
public PetFix(NMSStorage nms) {
|
||||
this.nms = nms;
|
||||
petOwnerPosition = getPetOwnerPosition();
|
||||
TAB.getInstance().debug("Loaded PetFix feature");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns position of pet owner field based on server version
|
||||
* @return position of pet owner field based on server version
|
||||
*/
|
||||
private int getPetOwnerPosition() {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
//1.17.x
|
||||
return 18;
|
||||
} else if (nms.getMinorVersion() >= 15) {
|
||||
//1.15.x, 1.16.x
|
||||
return 17;
|
||||
} else if (nms.getMinorVersion() >= 14) {
|
||||
//1.14.x
|
||||
return 16;
|
||||
} else if (nms.getMinorVersion() >= 10) {
|
||||
//1.10.x - 1.13.x
|
||||
return 14;
|
||||
} else {
|
||||
//1.9.x
|
||||
return 13;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a packet if previous one arrived with no delay to prevent double toggle on 1.16
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@Override
|
||||
public Object onPacketReceive(TabPlayer sender, Object packet) throws IllegalAccessException {
|
||||
if (nms.getClass("PacketPlayInUseEntity").isInstance(packet)) {
|
||||
if (lastInteractFix.containsKey(sender.getName()) && (System.currentTimeMillis() - lastInteractFix.get(sender.getName()) < 5)) {
|
||||
//last interact packet was sent right now, cancelling to prevent double-toggle due to this feature enabled
|
||||
return null;
|
||||
}
|
||||
if (isInteract(nms.getField("PacketPlayInUseEntity_ACTION").get(packet))) {
|
||||
//this is the first packet, saving player so the next packet can be cancelled
|
||||
lastInteractFix.put(sender.getName(), System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
private boolean isInteract(Object action) {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
return nms.getClass("PacketPlayInUseEntity$d").isInstance(action);
|
||||
} else {
|
||||
return action.toString().equals("INTERACT");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes pet owner field from datawatcher
|
||||
* @throws IllegalAccessException
|
||||
* @throws SecurityException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws InvocationTargetException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException, InstantiationException {
|
||||
if (nms.getClass("PacketPlayOutEntityMetadata").isInstance(packet)) {
|
||||
Object removedEntry = null;
|
||||
List<Object> items = (List<Object>) nms.getField("PacketPlayOutEntityMetadata_LIST").get(packet);
|
||||
if (items == null) return;
|
||||
for (Object item : items) {
|
||||
if (nms.getField("DataWatcherObject_SLOT").getInt(nms.getField("DataWatcherItem_TYPE").get(item)) == petOwnerPosition) {
|
||||
Object value = nms.getField("DataWatcherItem_VALUE").get(item);
|
||||
if (value instanceof java.util.Optional || value instanceof com.google.common.base.Optional) {
|
||||
removedEntry = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removedEntry != null) items.remove(removedEntry);
|
||||
}
|
||||
//<1.15
|
||||
Field datawatcher = nms.getField("PacketPlayOutSpawnEntityLiving_DATAWATCHER");
|
||||
if (nms.getClass("PacketPlayOutSpawnEntityLiving").isInstance(packet) && datawatcher != null) {
|
||||
DataWatcher watcher = DataWatcher.fromNMS(datawatcher.get(packet));
|
||||
DataWatcherItem petOwner = watcher.getItem(petOwnerPosition);
|
||||
if (petOwner != null && (petOwner.getValue() instanceof java.util.Optional || petOwner.getValue() instanceof com.google.common.base.Optional)) {
|
||||
watcher.removeValue(petOwnerPosition);
|
||||
nms.setField(packet, "PacketPlayOutSpawnEntityLiving_DATAWATCHER", watcher.toNMS());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabFeature getFeatureType() {
|
||||
return TabFeature.PET_NAME_FIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removing player data to not have a memory leak
|
||||
*/
|
||||
@Override
|
||||
public void onQuit(TabPlayer disconnectedPlayer) {
|
||||
lastInteractFix.remove(disconnectedPlayer.getName());
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.placeholders.Placeholder;
|
||||
|
||||
/**
|
||||
* TAB's expansion for PlaceholderAPI
|
||||
*/
|
||||
public class TabExpansion extends PlaceholderExpansion {
|
||||
|
||||
//plugin to take plugin.yml data from
|
||||
private JavaPlugin plugin;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs new instance of the class and registers it
|
||||
* @param plugin - plugin to take plugin.yml data from
|
||||
*/
|
||||
public TabExpansion(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean persist(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRegister(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthor(){
|
||||
return plugin.getDescription().getAuthors().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier(){
|
||||
return "tab";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion(){
|
||||
return plugin.getDescription().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onPlaceholderRequest(Player player, String identifier){
|
||||
if (player == null) return "";
|
||||
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
|
||||
if (identifier.equals("scoreboard_visible")) {
|
||||
return translate(p.isScoreboardVisible());
|
||||
}
|
||||
if (identifier.equals("bossbar_visible")) {
|
||||
return translate(p.hasBossbarVisible());
|
||||
}
|
||||
if (identifier.equals("ntpreview")) {
|
||||
return translate(p.isPreviewingNametag());
|
||||
}
|
||||
if (identifier.startsWith("replace_")) {
|
||||
return findReplacement("%" + identifier.substring(8) + "%", player);
|
||||
}
|
||||
if (identifier.startsWith("placeholder_")) {
|
||||
//using Property function for fast & easy handling of nested placeholders and different placeholder types
|
||||
return new Property(p, "%" + identifier.substring(12) + "%").get();
|
||||
}
|
||||
return getProperty(identifier, p);
|
||||
}
|
||||
|
||||
private String translate(boolean b) {
|
||||
return b ? "Enabled" : "Disabled";
|
||||
}
|
||||
|
||||
/**
|
||||
* Find replacement for specified placeholder
|
||||
* @param placeholder - placeholder to find replacement for
|
||||
* @param player - player to set placeholder for
|
||||
* @return replacement
|
||||
*/
|
||||
private String findReplacement(String placeholder, Player player) {
|
||||
if (!TAB.getInstance().isPremium()) return "Placeholder output replacements require premium version to work";
|
||||
String output = ((BukkitPlatform) TAB.getInstance().getPlatform()).setPlaceholders(player, placeholder);
|
||||
Map<Object, String> replacements = TAB.getInstance().getConfiguration().getPremiumConfig().getConfigurationSection("placeholder-output-replacements." + placeholder);
|
||||
return Placeholder.findReplacement(replacements, output).replace("%value%", output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value of defined property if found
|
||||
* @param name - property name
|
||||
* @param player - player to get property of
|
||||
* @return value of specified property
|
||||
*/
|
||||
private String getProperty(String name, TabPlayer player) {
|
||||
String propName = name.replace("_raw", "");
|
||||
Property prop = player.getProperty(propName);
|
||||
if (prop != null) {
|
||||
if (name.endsWith("_raw")) {
|
||||
return prop.getCurrentRawValue();
|
||||
}
|
||||
return prop.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +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.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitPacketBuilder;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An additional class with additional code for <1.9 servers due to an entity being required
|
||||
* An additional class with additional code for <1.9 servers due to an entity being required
|
||||
*/
|
||||
public class WitherBossBar implements Listener, Loadable {
|
||||
@RequiredArgsConstructor
|
||||
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
|
||||
|
||||
//distance of wither in blocks
|
||||
private static final int WITHER_DISTANCE = 75;
|
||||
|
||||
//main bossbar feature
|
||||
private BossBar mainFeature;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the class
|
||||
* @param mainFeature - main bossbar feature
|
||||
*/
|
||||
public WitherBossBar(TAB tab, JavaPlugin plugin) {
|
||||
this.mainFeature = (BossBar) tab.getFeatureManager().getFeature("bossbar");
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
//bar disappears in client after ~1 second of not seeing boss entity
|
||||
tab.getCPUManager().startRepeatingMeasuredTask(900, "refreshing bossbar", TabFeature.BOSSBAR, UsageType.TELEPORTING_ENTITY, this::teleport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
teleport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates wither location for all players
|
||||
*/
|
||||
private void teleport() {
|
||||
for (TabPlayer all : TAB.getInstance().getPlayers()) {
|
||||
if (all.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
|
||||
for (me.neznamy.tab.api.bossbar.BossBar l : all.getActiveBossBars()) {
|
||||
try {
|
||||
all.sendPacket(((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityTeleportPacket(l.getUniqueId().hashCode(), getWitherLocation(all)), TabFeature.BOSSBAR);
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to create PacketPlayOutEntityTeleport", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning wither as respawn screen destroys all entities in client
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask("processing PlayerRespawnEvent", TabFeature.BOSSBAR, UsageType.PLAYER_RESPAWN_EVENT, () -> mainFeature.detectBossBarsAndSend(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns location where wither should be located based on where player is looking
|
||||
* @param p - player to get wither location for
|
||||
* @return location of wither
|
||||
*/
|
||||
public Location getWitherLocation(TabPlayer p) {
|
||||
Player pl = (Player) p.getPlayer();
|
||||
Location loc = pl.getEyeLocation().add(pl.getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
|
||||
if (loc.getY() < 1) loc.setY(1);
|
||||
return loc;
|
||||
}
|
||||
@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);
|
||||
}
|
||||
}
|
||||
-387
@@ -1,387 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.GameMode;
|
||||
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.potion.PotionEffectType;
|
||||
|
||||
import me.neznamy.tab.api.ArmorStand;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitPacketBuilder;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.packets.IChatBaseComponent;
|
||||
|
||||
/**
|
||||
* A class representing an armor stand attached to a player (if the feature is enabled)
|
||||
*/
|
||||
public class BukkitArmorStand implements ArmorStand {
|
||||
|
||||
//owner of the armor stand
|
||||
private TabPlayer owner;
|
||||
|
||||
//instance of Bukkit player
|
||||
private Player player;
|
||||
|
||||
//offset in blocks, 0 for original height
|
||||
private double yOffset;
|
||||
|
||||
//entity ID of this armor stand
|
||||
private int entityId;
|
||||
|
||||
//unique ID of this armor stand
|
||||
private UUID uuid = UUID.randomUUID();
|
||||
|
||||
//sneaking flag of armor stands
|
||||
private boolean sneaking;
|
||||
|
||||
//armor stand visibility
|
||||
private boolean visible;
|
||||
|
||||
//list of players in entity tracking range (48 blocks)
|
||||
private Set<TabPlayer> nearbyPlayers = Collections.synchronizedSet(new HashSet<TabPlayer>());
|
||||
|
||||
//property dedicated to this armor stand
|
||||
private Property property;
|
||||
|
||||
//if offset is static or dynamic based on other armor stands
|
||||
private boolean staticOffset;
|
||||
|
||||
//if marker tag should be used for 1.8.x clients
|
||||
private boolean markerFor18x;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param owner - armor stand owner
|
||||
* @param property - property for armor stand's name
|
||||
* @param yOffset - offset in blocks
|
||||
* @param staticOffset - if offset is static or not
|
||||
*/
|
||||
public BukkitArmorStand(int entityId, TabPlayer owner, Property property, double yOffset, boolean staticOffset, boolean markerFor18x) {
|
||||
this.entityId = entityId;
|
||||
this.owner = owner;
|
||||
this.staticOffset = staticOffset;
|
||||
player = (Player) owner.getPlayer();
|
||||
this.yOffset = yOffset;
|
||||
this.property = property;
|
||||
this.markerFor18x = markerFor18x;
|
||||
visible = getVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
visible = getVisibility();
|
||||
updateMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStaticOffset() {
|
||||
return staticOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getOffset() {
|
||||
return yOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOffset(double offset) {
|
||||
if (yOffset == offset) return;
|
||||
yOffset = offset;
|
||||
for (TabPlayer all : getNearbyPlayers()) {
|
||||
all.sendPacket(getTeleportPacket(all), TabFeature.NAMETAGX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of packets to send to make armor stand spawn with metadata
|
||||
* @param viewer - viewer to apply relational placeholders for
|
||||
* @param addToRegistered - if player should be added to registered or not
|
||||
* @return List of packets that spawn the armor stand
|
||||
*/
|
||||
public Object[] getSpawnPackets(TabPlayer viewer) {
|
||||
visible = getVisibility();
|
||||
nearbyPlayers.add(viewer);
|
||||
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 15) {
|
||||
return new Object[] {
|
||||
getSpawnPacket(getArmorStandLocationFor(viewer), null),
|
||||
getMetadataPacket(dataWatcher)
|
||||
};
|
||||
} else {
|
||||
return new Object[] {
|
||||
getSpawnPacket(getArmorStandLocationFor(viewer), dataWatcher)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawn(TabPlayer viewer) {
|
||||
for (Object packet : getSpawnPackets(viewer)) {
|
||||
viewer.sendPacket(packet, TabFeature.NAMETAGX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns location where armor stand should be for specified viewer
|
||||
* @param viewer - player to get location for
|
||||
* @return location of armor stand
|
||||
*/
|
||||
protected Location getArmorStandLocationFor(TabPlayer viewer) {
|
||||
return viewer.getVersion().getMinorVersion() == 8 && !markerFor18x ? getLocation().clone().add(0,-2,0) : getLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(TabPlayer viewer) {
|
||||
nearbyPlayers.remove(viewer);
|
||||
viewer.sendPacket(getDestroyPacket(), TabFeature.NAMETAGX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleport() {
|
||||
for (TabPlayer all : getNearbyPlayers()) {
|
||||
all.sendPacket(getTeleportPacket(all), TabFeature.NAMETAGX);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleport(TabPlayer viewer) {
|
||||
viewer.sendPacket(getTeleportPacket(viewer), TabFeature.NAMETAGX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sneak(boolean sneaking) {
|
||||
if (this.sneaking == sneaking) return; //idk
|
||||
this.sneaking = sneaking;
|
||||
for (TabPlayer viewer : getNearbyPlayers()) {
|
||||
if (viewer.getVersion().getMinorVersion() == 14 && !TAB.getInstance().getConfiguration().isArmorStandsAlwaysVisible()) {
|
||||
//1.14.x client sided bug, despawning completely
|
||||
if (sneaking) {
|
||||
viewer.sendPacket(getDestroyPacket(), TabFeature.NAMETAGX);
|
||||
} else {
|
||||
spawn(viewer);
|
||||
}
|
||||
} else {
|
||||
//respawning so there's no animation and it's instant
|
||||
viewer.sendPacket(getDestroyPacket(), TabFeature.NAMETAGX);
|
||||
Runnable spawn = () -> spawn(viewer);
|
||||
if (viewer.getVersion().getMinorVersion() == 8) {
|
||||
//1.8.0 client sided bug
|
||||
TAB.getInstance().getCPUManager().runTaskLater(50, "compensating for 1.8.0 bugs", TabFeature.NAMETAGX, UsageType.V1_8_0_BUG_COMPENSATION, spawn);
|
||||
} else {
|
||||
spawn.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
for (TabPlayer all : TAB.getInstance().getPlayers()) all.sendPacket(getDestroyPacket(), TabFeature.NAMETAGX);
|
||||
nearbyPlayers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVisibility(boolean force) {
|
||||
boolean visibility = getVisibility();
|
||||
if (visible != visibility || force) {
|
||||
visible = visibility;
|
||||
updateMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns teleport packet for specified viewer
|
||||
* @param viewer - player to get location for
|
||||
* @return teleport packet
|
||||
*/
|
||||
protected Object getTeleportPacket(TabPlayer viewer) {
|
||||
try {
|
||||
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityTeleportPacket(entityId, getArmorStandLocationFor(viewer));
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to create PacketPlayOutEntityTeleport", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns destroy packet
|
||||
* @return destroy packet
|
||||
*/
|
||||
protected Object getDestroyPacket() {
|
||||
try {
|
||||
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityDestroyPacket(entityId);
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to create PacketPlayOutEntityDestroy", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns metadata packet with specified datawatcher
|
||||
* @param dataWatcher - datawatcher
|
||||
* @return metadata packet
|
||||
*/
|
||||
protected Object getMetadataPacket(DataWatcher dataWatcher) {
|
||||
try {
|
||||
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityMetadataPacket(entityId, dataWatcher);
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to create PacketPlayOutEntityMetadata", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns spawn packet with specified parameters
|
||||
* @param loc - location to spawn at
|
||||
* @param dataWatcher - datawatcher
|
||||
* @return spawn packet
|
||||
*/
|
||||
protected Object getSpawnPacket(Location loc, DataWatcher dataWatcher) {
|
||||
try {
|
||||
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntitySpawnPacket(entityId, uuid, EntityType.ARMOR_STAND, loc, dataWatcher);
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to create PacketPlayOutSpawnEntityLiving", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates armor stand's metadata
|
||||
*/
|
||||
protected void updateMetadata() {
|
||||
for (TabPlayer viewer : getNearbyPlayers()) {
|
||||
viewer.sendPacket(getMetadataPacket(createDataWatcher(property.getFormat(viewer), viewer)), TabFeature.NAMETAGX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns general visibility rule for everyone with limited info
|
||||
* @return true if should be visible, false if not
|
||||
*/
|
||||
protected boolean getVisibility() {
|
||||
if (((BukkitTabPlayer)owner).isDisguised() || (((NameTagX)TAB.getInstance().getFeatureManager().getFeature("nametagx")).getPlayersOnBoats().contains(owner))) return false;
|
||||
if (TAB.getInstance().getConfiguration().isArmorStandsAlwaysVisible()) return true;
|
||||
return !player.hasPotionEffect(PotionEffectType.INVISIBILITY) && player.getGameMode() != GameMode.SPECTATOR && !owner.hasHiddenNametag() && property.get().length() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns general location where armor stand should be at time of calling
|
||||
* @return Location where armor stand should be for everyone
|
||||
*/
|
||||
protected Location getLocation() {
|
||||
double x = player.getLocation().getX();
|
||||
double y = getY() + yOffset + 2;
|
||||
double z = player.getLocation().getZ();
|
||||
if (player.isSleeping()) {
|
||||
y -= 1.76;
|
||||
} else {
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
|
||||
y -= (sneaking ? 0.45 : 0.18);
|
||||
} else {
|
||||
y -= (sneaking ? 0.30 : 0.18);
|
||||
}
|
||||
}
|
||||
return new Location(null,x,y,z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Y where player is based on player's vehicle due to bukkit API bug
|
||||
* @return correct player's Y
|
||||
*/
|
||||
protected double getY() {
|
||||
//1.14+ server sided bug
|
||||
Entity vehicle = player.getVehicle();
|
||||
if (vehicle != null) {
|
||||
if (vehicle.getType().toString().contains("HORSE")) { //covering all 3 horse types
|
||||
return vehicle.getLocation().getY() + 0.85;
|
||||
}
|
||||
if (vehicle.getType().toString().equals("DONKEY")) { //1.11+
|
||||
return vehicle.getLocation().getY() + 0.525;
|
||||
}
|
||||
if (vehicle.getType() == EntityType.PIG) {
|
||||
return vehicle.getLocation().getY() + 0.325;
|
||||
}
|
||||
if (vehicle.getType().toString().equals("STRIDER")) { //1.16+
|
||||
return vehicle.getLocation().getY() + 1.15;
|
||||
}
|
||||
}
|
||||
//1.13+ swimming or 1.9+ flying with elytra
|
||||
if (isSwimming() || (NMSStorage.getInstance().getMinorVersion() >= 9 && player.isGliding())) {
|
||||
return player.getLocation().getY()-1.22;
|
||||
}
|
||||
return player.getLocation().getY();
|
||||
}
|
||||
|
||||
private boolean isSwimming() {
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
|
||||
return NMSStorage.getInstance().getMinorVersion() == 13 && player.isSwimming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFromRegistered(TabPlayer viewer) {
|
||||
nearbyPlayers.remove(viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates data watcher with specified display name for viewer
|
||||
* @param displayName - armor stand name
|
||||
* @param viewer - player to apply checks against
|
||||
* @return datawatcher for viewer
|
||||
*/
|
||||
protected DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
|
||||
DataWatcher datawatcher = new DataWatcher();
|
||||
|
||||
byte flag = 32; //invisible
|
||||
if (sneaking) flag += (byte)2;
|
||||
datawatcher.helper().setEntityFlags(flag);
|
||||
datawatcher.helper().setCustomName(displayName, viewer.getVersion());
|
||||
|
||||
boolean visibility;
|
||||
if (isNameVisiblyEmpty(displayName) || !((Player) viewer.getPlayer()).canSee(player) || owner.hasHiddenNametag(viewer.getUniqueId())) {
|
||||
visibility = false;
|
||||
} else {
|
||||
visibility = visible;
|
||||
}
|
||||
datawatcher.helper().setCustomNameVisible(visibility);
|
||||
|
||||
if (viewer.getVersion().getMinorVersion() > 8 || markerFor18x) datawatcher.helper().setArmorStandFlags((byte)16);
|
||||
return datawatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<TabPlayer> getNearbyPlayers(){
|
||||
synchronized (nearbyPlayers) {
|
||||
return new HashSet<>(nearbyPlayers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if display name is in fact empty, for example only containing color codes
|
||||
* @param displayName - string to check
|
||||
* @return true if it's empty, false if not
|
||||
*/
|
||||
private boolean isNameVisiblyEmpty(String displayName) {
|
||||
return IChatBaseComponent.fromColoredText(displayName).toRawText().replace(" ", "").length() == 0;
|
||||
}
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
|
||||
/**
|
||||
* The event listener part for securing proper functionality of armor stands
|
||||
*/
|
||||
public class EventListener implements Listener {
|
||||
|
||||
//the nametag feature handler
|
||||
private NameTagX feature;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param feature - nametagx feature handler
|
||||
*/
|
||||
public EventListener(NameTagX feature) {
|
||||
this.feature = feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sneak event listener to despawn & spawn armor stands to skip animation
|
||||
* @param e - sneak event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onSneak(PlayerToggleSneakEvent e) {
|
||||
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (p == null || !p.isLoaded() || feature.isInDisabledWorld(p)) return;
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask("processing PlayerToggleSneakEvent", TabFeature.NAMETAGX, UsageType.PLAYER_TOGGLE_SNEAK_EVENT, () -> {
|
||||
if (p.getArmorStandManager() != null) p.getArmorStandManager().sneak(e.isSneaking());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning armor stands as respawn screen destroys all entities in client
|
||||
* @param e - respawn event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask("processing PlayerRespawnEvent", TabFeature.NAMETAGX, UsageType.PLAYER_RESPAWN_EVENT, () -> {
|
||||
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (feature.isInDisabledWorld(respawned)) return;
|
||||
respawned.getArmorStandManager().teleport();
|
||||
});
|
||||
}
|
||||
}
|
||||
-441
@@ -1,441 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
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.event.HandlerList;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.ArmorStand;
|
||||
import me.neznamy.tab.api.ArmorStandManager;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.PropertyUtils;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.features.NameTag;
|
||||
|
||||
/**
|
||||
* The core class for unlimited nametag mode
|
||||
*/
|
||||
public class NameTagX extends NameTag {
|
||||
|
||||
//config options
|
||||
private boolean markerFor18x;
|
||||
private boolean disableOnBoats;
|
||||
private double spaceBetweenLines;
|
||||
|
||||
//list of worlds with unlimited nametag mode disabled
|
||||
protected List<String> disabledUnlimitedWorlds;
|
||||
|
||||
//list of defined dynamic lines
|
||||
private List<String> dynamicLines = Arrays.asList(PropertyUtils.BELOWNAME, PropertyUtils.NAMETAG, PropertyUtils.ABOVENAME);
|
||||
|
||||
//map of defined static lines
|
||||
private Map<String, Object> staticLines = new ConcurrentHashMap<>();
|
||||
|
||||
//entity id counter to pick unique entity IDs
|
||||
private int idCounter = 2000000000;
|
||||
|
||||
//player data by entityId, used for better performance
|
||||
private Map<Integer, TabPlayer> entityIdMap = new ConcurrentHashMap<>();
|
||||
|
||||
//map of vehicles carrying players
|
||||
private Map<Integer, List<Entity>> vehicles = new ConcurrentHashMap<>();
|
||||
|
||||
//bukkit event listener
|
||||
private EventListener eventListener;
|
||||
|
||||
//list of players currently on boats
|
||||
private List<TabPlayer> playersOnBoats = new ArrayList<>();
|
||||
|
||||
//list of players currently in a vehicle
|
||||
private Map<TabPlayer, Entity> playersInVehicle = new ConcurrentHashMap<>();
|
||||
|
||||
private Map<TabPlayer, Location> playerLocations = new ConcurrentHashMap<>();
|
||||
|
||||
private Set<TabPlayer> playersInDisabledUnlimitedWorlds = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
* @param plugin - plugin instance
|
||||
* @param nms - nms storage
|
||||
* @param tab - tab instance
|
||||
*/
|
||||
public NameTagX(JavaPlugin plugin, NMSStorage nms, TAB tab) {
|
||||
super(tab);
|
||||
markerFor18x = tab.getConfiguration().getConfig().getBoolean("unlimited-nametag-prefix-suffix-mode.use-marker-tag-for-1-8-x-clients", false);
|
||||
disableOnBoats = tab.getConfiguration().getConfig().getBoolean("unlimited-nametag-prefix-suffix-mode.disable-on-boats", true);
|
||||
spaceBetweenLines = tab.getConfiguration().getConfig().getDouble("unlimited-nametag-prefix-suffix-mode.space-between-lines", 0.22);
|
||||
disabledUnlimitedWorlds = tab.getConfiguration().getConfig().getStringList("disable-features-in-worlds.unlimited-nametags", Arrays.asList("disabledworld"));
|
||||
if (tab.getConfiguration().getPremiumConfig() != null) {
|
||||
List<String> realList = tab.getConfiguration().getPremiumConfig().getStringList("unlimited-nametag-mode-dynamic-lines", Arrays.asList(PropertyUtils.ABOVENAME, PropertyUtils.NAMETAG, PropertyUtils.BELOWNAME, "another"));
|
||||
dynamicLines = new ArrayList<>();
|
||||
dynamicLines.addAll(realList);
|
||||
Collections.reverse(dynamicLines);
|
||||
staticLines = tab.getConfiguration().getPremiumConfig().getConfigurationSection("unlimited-nametag-mode-static-lines");
|
||||
}
|
||||
refreshUsedPlaceholders();
|
||||
eventListener = new EventListener(this);
|
||||
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
|
||||
tab.getFeatureManager().registerFeature("nametagx-packet", new PacketListener(this, nms, tab));
|
||||
tab.debug(String.format("Loaded Unlimited nametag feature with parameters markerFor18x=%s, disableOnBoats=%s, spaceBetweenLines=%s, disabledUnlimitedWorlds=%s",
|
||||
markerFor18x, disableOnBoats, spaceBetweenLines, disabledUnlimitedWorlds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
super.load();
|
||||
for (TabPlayer all : tab.getPlayers()) {
|
||||
getEntityIdMap().put(((Player) all.getPlayer()).getEntityId(), all);
|
||||
loadArmorStands(all);
|
||||
if (isDisabledWorld(disabledUnlimitedWorlds, all.getWorldName())) {
|
||||
playersInDisabledUnlimitedWorlds.add(all);
|
||||
}
|
||||
if (isInDisabledWorld(all)) continue;
|
||||
loadPassengers(all);
|
||||
for (TabPlayer viewer : tab.getPlayers()) {
|
||||
spawnArmorStands(all, viewer, false);
|
||||
}
|
||||
}
|
||||
startVisibilityRefreshTask();
|
||||
startVehicleTickingTask();
|
||||
}
|
||||
|
||||
private void startVisibilityRefreshTask() {
|
||||
tab.getCPUManager().startRepeatingMeasuredTask(500, "refreshing nametag visibility", getFeatureType(), UsageType.REFRESHING_NAMETAG_VISIBILITY_AND_COLLISION, () -> {
|
||||
|
||||
for (TabPlayer p : tab.getPlayers()) {
|
||||
if (!p.isLoaded() || isInDisabledWorld(p)) continue;
|
||||
p.getArmorStandManager().updateVisibility(false);
|
||||
if (disableOnBoats) processBoats(p);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void processBoats(TabPlayer p) {
|
||||
boolean onBoat = ((Player)p.getPlayer()).getVehicle() != null && ((Player)p.getPlayer()).getVehicle().getType() == EntityType.BOAT;
|
||||
if (onBoat) {
|
||||
if (!getPlayersOnBoats().contains(p)) {
|
||||
getPlayersOnBoats().add(p);
|
||||
updateTeamData(p);
|
||||
}
|
||||
} else {
|
||||
if (getPlayersOnBoats().contains(p)) {
|
||||
getPlayersOnBoats().remove(p);
|
||||
updateTeamData(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startVehicleTickingTask() {
|
||||
tab.getCPUManager().startRepeatingMeasuredTask(100, "ticking vehicles", TabFeature.NAMETAGX, UsageType.TICKING_VEHICLES, () -> {
|
||||
|
||||
for (TabPlayer p : TAB.getInstance().getPlayers()) {
|
||||
if (!p.isLoaded()) continue;
|
||||
if (isInDisabledWorld(p)) {
|
||||
playersInVehicle.remove(p);
|
||||
playerLocations.remove(p);
|
||||
} else {
|
||||
processVehicles(p);
|
||||
if (!playerLocations.containsKey(p) || !playerLocations.get(p).equals(((Player)p.getPlayer()).getLocation())) {
|
||||
playerLocations.put(p, ((Player)p.getPlayer()).getLocation());
|
||||
processPassengers((Entity) p.getPlayer());
|
||||
//also updating position if player is previewing since we're here as the code would be same if we want to avoid listening to move event
|
||||
if (p.isPreviewingNametag() && p.getArmorStandManager() != null) {
|
||||
p.getArmorStandManager().teleport(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for vehicle changes of player and sends packets if needed
|
||||
* @param p - player to check
|
||||
*/
|
||||
private void processVehicles(TabPlayer p) {
|
||||
Entity vehicle = ((Player)p.getPlayer()).getVehicle();
|
||||
if (playersInVehicle.containsKey(p) && vehicle == null) {
|
||||
//vehicle exit
|
||||
getVehicles().remove(playersInVehicle.get(p).getEntityId());
|
||||
p.getArmorStandManager().teleport();
|
||||
playersInVehicle.remove(p);
|
||||
}
|
||||
if (!playersInVehicle.containsKey(p) && vehicle != null) {
|
||||
//vehicle enter
|
||||
getVehicles().put(vehicle.getEntityId(), getPassengers(vehicle));
|
||||
p.getArmorStandManager().teleport();
|
||||
playersInVehicle.put(p, vehicle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports armor stands of all passengers on specified vehicle
|
||||
* @param vehicle - entity to check passengers of
|
||||
*/
|
||||
private void processPassengers(Entity vehicle) {
|
||||
for (Entity passenger : getPassengers(vehicle)) {
|
||||
if (passenger instanceof Player) {
|
||||
TabPlayer pl = TAB.getInstance().getPlayer(passenger.getUniqueId());
|
||||
pl.getArmorStandManager().teleport();
|
||||
} else {
|
||||
processPassengers(passenger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
super.unload();
|
||||
HandlerList.unregisterAll(eventListener);
|
||||
for (TabPlayer p : tab.getPlayers()) {
|
||||
if (p.getArmorStandManager() != null) p.getArmorStandManager().destroy();
|
||||
}
|
||||
getEntityIdMap().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onJoin(TabPlayer connectedPlayer) {
|
||||
super.onJoin(connectedPlayer);
|
||||
getEntityIdMap().put(((Player) connectedPlayer.getPlayer()).getEntityId(), connectedPlayer);
|
||||
loadArmorStands(connectedPlayer);
|
||||
if (isDisabledWorld(disabledUnlimitedWorlds, connectedPlayer.getWorldName())) {
|
||||
playersInDisabledUnlimitedWorlds.add(connectedPlayer);
|
||||
return;
|
||||
}
|
||||
if (isInDisabledWorld(connectedPlayer)) return;
|
||||
loadPassengers(connectedPlayer);
|
||||
for (TabPlayer viewer : tab.getPlayers()) {
|
||||
spawnArmorStands(connectedPlayer, viewer, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns flat distance between two players ignoring Y value
|
||||
* @param player1 - first player
|
||||
* @param player2 - second player
|
||||
* @return flat distance in blocks
|
||||
*/
|
||||
private double getDistance(TabPlayer player1, TabPlayer player2) {
|
||||
Location loc1 = ((Player) player1.getPlayer()).getLocation();
|
||||
Location loc2 = ((Player) player2.getPlayer()).getLocation();
|
||||
return Math.sqrt(Math.pow(loc1.getX()-loc2.getX(), 2) + Math.pow(loc1.getZ()-loc2.getZ(), 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all passengers riding this player and adds them to vehicle list
|
||||
* @param p - player to load passengers of
|
||||
*/
|
||||
private void loadPassengers(TabPlayer p) {
|
||||
if (((Entity) p.getPlayer()).getVehicle() == null) return;
|
||||
Entity vehicle = ((Entity) p.getPlayer()).getVehicle();
|
||||
getVehicles().put(vehicle.getEntityId(), getPassengers(vehicle));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQuit(TabPlayer disconnectedPlayer) {
|
||||
super.onQuit(disconnectedPlayer);
|
||||
for (TabPlayer all : tab.getPlayers()) {
|
||||
if (all.getArmorStandManager() != null) all.getArmorStandManager().unregisterPlayer(disconnectedPlayer);
|
||||
}
|
||||
getEntityIdMap().remove(((Player) disconnectedPlayer.getPlayer()).getEntityId());
|
||||
playersInVehicle.remove(disconnectedPlayer);
|
||||
playerLocations.remove(disconnectedPlayer);
|
||||
playersInDisabledUnlimitedWorlds.remove(disconnectedPlayer);
|
||||
tab.getCPUManager().runTaskLater(100, "processing onQuit", getFeatureType(), UsageType.PLAYER_QUIT_EVENT, () -> disconnectedPlayer.getArmorStandManager().destroy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWorldChange(TabPlayer p, String from, String to) {
|
||||
super.onWorldChange(p, from, to);
|
||||
if (isDisabledWorld(disabledWorlds, p.getWorldName())) {
|
||||
playersInDisabledWorlds.add(p);
|
||||
} else {
|
||||
playersInDisabledWorlds.remove(p);
|
||||
}
|
||||
Set<TabPlayer> nearby = p.getArmorStandManager().getNearbyPlayers();
|
||||
p.getArmorStandManager().destroy();
|
||||
loadArmorStands(p);
|
||||
loadPassengers(p);
|
||||
for (TabPlayer viewer : tab.getPlayers()) {
|
||||
viewer.getArmorStandManager().destroy(p);
|
||||
if (nearby.contains(viewer) && to.equals(viewer.getWorldName())) spawnArmorStands(p, viewer, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns armor stands of specified owner to viewer and, mutually if set
|
||||
* @param owner - armor stand owner
|
||||
* @param viewer - player to spawn armor stands for
|
||||
* @param sendMutually - if packets should be sent both ways
|
||||
*/
|
||||
private void spawnArmorStands(TabPlayer owner, TabPlayer viewer, boolean sendMutually) {
|
||||
if (owner == viewer) return; //not displaying own armorstands
|
||||
if (((Player) viewer.getPlayer()).getWorld() != ((Player) owner.getPlayer()).getWorld()) return; //different world
|
||||
if (isInDisabledWorld(owner)) return;
|
||||
if (getDistance(viewer, owner) <= 48) {
|
||||
if (((Player)viewer.getPlayer()).canSee((Player)owner.getPlayer()) && !owner.isVanished()) owner.getArmorStandManager().spawn(viewer);
|
||||
if (sendMutually && viewer.getArmorStandManager() != null && ((Player)owner.getPlayer()).canSee((Player)viewer.getPlayer())
|
||||
&& !viewer.isVanished()) viewer.getArmorStandManager().spawn(owner);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts and loads armor stands from config
|
||||
* @param pl - player to load
|
||||
*/
|
||||
public void loadArmorStands(TabPlayer pl) {
|
||||
pl.setArmorStandManager(new ArmorStandManager());
|
||||
pl.setProperty(PropertyUtils.NAMETAG, pl.getProperty(PropertyUtils.TAGPREFIX).getCurrentRawValue() + pl.getProperty(PropertyUtils.CUSTOMTAGNAME).getCurrentRawValue() + pl.getProperty(PropertyUtils.TAGSUFFIX).getCurrentRawValue());
|
||||
double height = 0;
|
||||
for (String line : dynamicLines) {
|
||||
Property p = pl.getProperty(line);
|
||||
if (p.getCurrentRawValue().length() == 0) continue;
|
||||
pl.getArmorStandManager().addArmorStand(line, new BukkitArmorStand(idCounter++, pl, p, height, false, markerFor18x));
|
||||
height += spaceBetweenLines;
|
||||
}
|
||||
for (Entry<String, Object> line : staticLines.entrySet()) {
|
||||
Property p = pl.getProperty(line.getKey());
|
||||
if (p.getCurrentRawValue().length() == 0) continue;
|
||||
pl.getArmorStandManager().addArmorStand(line.getKey(), new BukkitArmorStand(idCounter++, pl, p, Double.parseDouble(line.getValue().toString()), true, markerFor18x));
|
||||
}
|
||||
fixArmorStandHeights(pl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes heights of all armor stands of specified player due to dynamic lines
|
||||
* @param p - player to fix armor stands heights for
|
||||
*/
|
||||
public void fixArmorStandHeights(TabPlayer p) {
|
||||
p.getArmorStandManager().refresh();
|
||||
double currentY = -spaceBetweenLines;
|
||||
for (ArmorStand as : p.getArmorStandManager().getArmorStands()) {
|
||||
if (as.hasStaticOffset()) continue;
|
||||
if (as.getProperty().get().length() != 0) {
|
||||
currentY += spaceBetweenLines;
|
||||
as.setOffset(currentY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(TabPlayer refreshed, boolean force) {
|
||||
super.refresh(refreshed, force);
|
||||
if (isInDisabledWorld(refreshed)) return;
|
||||
if (force) {
|
||||
refreshed.getArmorStandManager().destroy();
|
||||
loadArmorStands(refreshed);
|
||||
loadPassengers(refreshed);
|
||||
for (TabPlayer viewer : tab.getPlayers()) {
|
||||
if (viewer == refreshed) continue;
|
||||
if (viewer.getWorldName().equals(refreshed.getWorldName())) {
|
||||
refreshed.getArmorStandManager().spawn(viewer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boolean fix = false;
|
||||
for (ArmorStand as : refreshed.getArmorStandManager().getArmorStands()) {
|
||||
if (as.getProperty().update()) {
|
||||
as.refresh();
|
||||
fix = true;
|
||||
}
|
||||
}
|
||||
if (fix) fixArmorStandHeights(refreshed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates raw values of properties for specified player
|
||||
* @param p - player to update
|
||||
*/
|
||||
@Override
|
||||
public void updateProperties(TabPlayer p) {
|
||||
super.updateProperties(p);
|
||||
p.loadPropertyFromConfig(PropertyUtils.CUSTOMTAGNAME, p.getName());
|
||||
p.setProperty(PropertyUtils.NAMETAG, p.getProperty(PropertyUtils.TAGPREFIX).getCurrentRawValue() + p.getProperty(PropertyUtils.CUSTOMTAGNAME).getCurrentRawValue() + p.getProperty(PropertyUtils.TAGSUFFIX).getCurrentRawValue());
|
||||
for (String property : dynamicLines) {
|
||||
if (!property.equals(PropertyUtils.NAMETAG)) p.loadPropertyFromConfig(property);
|
||||
}
|
||||
for (String property : staticLines.keySet()) {
|
||||
if (!property.equals(PropertyUtils.NAMETAG)) p.loadPropertyFromConfig(property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of all passengers on specified vehicle
|
||||
* @param vehicle - vehicle to check passengers of
|
||||
* @return list of passengers
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<Entity> getPassengers(Entity vehicle){
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 11) {
|
||||
return vehicle.getPassengers();
|
||||
} else {
|
||||
if (vehicle.getPassenger() != null) {
|
||||
return Arrays.asList(vehicle.getPassenger());
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshUsedPlaceholders() {
|
||||
usedPlaceholders = new HashSet<>(tab.getConfiguration().getConfig().getUsedPlaceholderIdentifiersRecursive(PropertyUtils.TAGPREFIX, PropertyUtils.CUSTOMTAGNAME, PropertyUtils.TAGSUFFIX));
|
||||
for (String line : dynamicLines) {
|
||||
usedPlaceholders.addAll(tab.getConfiguration().getConfig().getUsedPlaceholderIdentifiersRecursive(line));
|
||||
}
|
||||
for (String line : staticLines.keySet()) {
|
||||
usedPlaceholders.addAll(tab.getConfiguration().getConfig().getUsedPlaceholderIdentifiersRecursive(line));
|
||||
}
|
||||
for (TabPlayer p : tab.getPlayers()) {
|
||||
usedPlaceholders.addAll(tab.getPlaceholderManager().getUsedPlaceholderIdentifiersRecursive(p.getProperty(PropertyUtils.TAGPREFIX).getCurrentRawValue(),
|
||||
p.getProperty(PropertyUtils.CUSTOMTAGNAME).getCurrentRawValue(), p.getProperty(PropertyUtils.TAGSUFFIX).getCurrentRawValue(),
|
||||
p.getProperty(PropertyUtils.ABOVENAME).getCurrentRawValue(), p.getProperty(PropertyUtils.BELOWNAME).getCurrentRawValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabFeature getFeatureType() {
|
||||
return TabFeature.NAMETAGX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getTeamVisibility(TabPlayer p, TabPlayer viewer) {
|
||||
//only visible if player is on boat & config option is enabled and player is not invisible (1.8 bug) or feature is disabled
|
||||
return (getPlayersOnBoats().contains(p) && !invisiblePlayers.contains(p.getName())) || isInDisabledWorld(p);
|
||||
}
|
||||
|
||||
public Map<Integer, TabPlayer> getEntityIdMap() {
|
||||
return entityIdMap;
|
||||
}
|
||||
|
||||
public Map<Integer, List<Entity>> getVehicles() {
|
||||
return vehicles;
|
||||
}
|
||||
|
||||
public List<TabPlayer> getPlayersOnBoats() {
|
||||
return playersOnBoats;
|
||||
}
|
||||
|
||||
public boolean isInDisabledWorld(TabPlayer p) {
|
||||
return playersInDisabledWorlds.contains(p) || isDisabledWorld(disabledUnlimitedWorlds, p.getWorldName());
|
||||
}
|
||||
}
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.features.types.packet.RawPacketListener;
|
||||
|
||||
/**
|
||||
* The packet listening part for securing proper functionality of armor stands
|
||||
* Bukkit events are too unreliable and delayed/ahead which causes desync
|
||||
*/
|
||||
public class PacketListener implements RawPacketListener {
|
||||
|
||||
//main feature
|
||||
private NameTagX nameTagX;
|
||||
|
||||
//nms storage
|
||||
private NMSStorage nms;
|
||||
|
||||
//tab instance
|
||||
private TAB tab;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
* @param nameTagX - main feature
|
||||
* @param nms - nms storage
|
||||
* @param tab - tab instance
|
||||
*/
|
||||
public PacketListener(NameTagX nameTagX, NMSStorage nms, TAB tab) {
|
||||
this.nameTagX = nameTagX;
|
||||
this.nms = nms;
|
||||
this.tab = tab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onPacketReceive(TabPlayer sender, Object packet) throws IllegalAccessException {
|
||||
if (sender.getVersion().getMinorVersion() == 8 && nms.getClass("PacketPlayInUseEntity").isInstance(packet)) {
|
||||
int entityId = nms.getField("PacketPlayInUseEntity_ENTITY").getInt(packet);
|
||||
TabPlayer attacked = null;
|
||||
for (TabPlayer all : tab.getPlayers()) {
|
||||
if (all.isLoaded() && all.getArmorStandManager().hasArmorStandWithID(entityId)) {
|
||||
attacked = all;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attacked != null && attacked != sender) {
|
||||
nms.setField(packet, "PacketPlayInUseEntity_ENTITY", ((Player) attacked.getPlayer()).getEntityId());
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws IllegalAccessException {
|
||||
if (receiver.getVersion().getMinorVersion() < 8) return;
|
||||
//using bukkit player to check world due to old data on world change due to asynchronous processing & world name changing
|
||||
String world = ((Player)receiver.getPlayer()).getWorld().getName();
|
||||
if (!receiver.isLoaded() || nameTagX.getPlayersInDisabledWorlds().contains(receiver) || nameTagX.isDisabledWorld(nameTagX.disabledUnlimitedWorlds, world)) return;
|
||||
if (nms.getClass("PacketPlayOutEntity").isInstance(packet) && !nms.getClass("PacketPlayOutEntityLook").isInstance(packet)) { //ignoring head rotation only packets
|
||||
onEntityMove(receiver, nms.getField("PacketPlayOutEntity_ENTITYID").getInt(packet));
|
||||
} else if (nms.getClass("PacketPlayOutEntityTeleport").isInstance(packet)) {
|
||||
onEntityMove(receiver, nms.getField("PacketPlayOutEntityTeleport_ENTITYID").getInt(packet));
|
||||
} else if (nms.getClass("PacketPlayOutNamedEntitySpawn").isInstance(packet)) {
|
||||
onEntitySpawn(receiver, nms.getField("PacketPlayOutNamedEntitySpawn_ENTITYID").getInt(packet));
|
||||
} else if (nms.getClass("PacketPlayOutEntityDestroy").isInstance(packet)) {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
try {
|
||||
onEntityDestroy(receiver, (List<Integer>) nms.getField("PacketPlayOutEntityDestroy_ENTITIES").get(packet));
|
||||
} catch (ClassCastException e) {
|
||||
//1.17.0
|
||||
onEntityDestroy(receiver, nms.getField("PacketPlayOutEntityDestroy_ENTITIES").getInt(packet));
|
||||
}
|
||||
} else {
|
||||
onEntityDestroy(receiver, (int[]) nms.getField("PacketPlayOutEntityDestroy_ENTITIES").get(packet));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes entity move packet
|
||||
* @param receiver - packet receiver
|
||||
* @param entityId - entity that moved
|
||||
*/
|
||||
private void onEntityMove(TabPlayer receiver, int entityId) {
|
||||
TabPlayer pl = nameTagX.getEntityIdMap().get(entityId);
|
||||
List<Entity> vehicleList;
|
||||
if (pl != null) {
|
||||
//player moved
|
||||
tab.getCPUManager().runMeasuredTask("processing EntityMove", getFeatureType(), UsageType.PACKET_ENTITY_MOVE, () -> pl.getArmorStandManager().teleport(receiver));
|
||||
} else if ((vehicleList = nameTagX.getVehicles().get(entityId)) != null){
|
||||
//a vehicle carrying something moved
|
||||
for (Entity entity : vehicleList) {
|
||||
TabPlayer passenger = nameTagX.getEntityIdMap().get(entity.getEntityId());
|
||||
if (passenger != null && passenger.getArmorStandManager() != null) {
|
||||
tab.getCPUManager().runMeasuredTask("processing EntityMove", getFeatureType(), UsageType.PACKET_ENTITY_MOVE, () -> passenger.getArmorStandManager().teleport(receiver));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onEntitySpawn(TabPlayer receiver, int entityId) {
|
||||
TabPlayer spawnedPlayer = nameTagX.getEntityIdMap().get(entityId);
|
||||
if (spawnedPlayer != null && spawnedPlayer.isLoaded()) {
|
||||
tab.getCPUManager().runMeasuredTask("processing NamedEntitySpawn", getFeatureType(), UsageType.PACKET_NAMED_ENTITY_SPAWN, () -> spawnedPlayer.getArmorStandManager().spawn(receiver));
|
||||
}
|
||||
}
|
||||
|
||||
private void onEntityDestroy(TabPlayer receiver, List<Integer> entities) {
|
||||
for (int entity : entities) {
|
||||
onEntityDestroy(receiver, entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void onEntityDestroy(TabPlayer receiver, int... entities) {
|
||||
for (int entity : entities) {
|
||||
onEntityDestroy(receiver, entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void onEntityDestroy(TabPlayer receiver, int entity) {
|
||||
TabPlayer despawnedPlayer = nameTagX.getEntityIdMap().get(entity);
|
||||
if (despawnedPlayer != null && despawnedPlayer.isLoaded())
|
||||
tab.getCPUManager().runMeasuredTask("processing EntityDestroy", getFeatureType(), UsageType.PACKET_ENTITY_DESTROY, () -> despawnedPlayer.getArmorStandManager().destroy(receiver));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabFeature getFeatureType() {
|
||||
return TabFeature.NAMETAGX;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,524 +1,123 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
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 io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.Main;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherRegistry;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
/**
|
||||
* Class holding all NMS classes, methods, fields and constructors used by TAB.
|
||||
*/
|
||||
public class NMSStorage {
|
||||
|
||||
//instance of this class
|
||||
private static NMSStorage instance;
|
||||
/** Instance of this class */
|
||||
@Getter @Setter private static NMSStorage instance;
|
||||
|
||||
//data watcher registry
|
||||
private DataWatcherRegistry dataWatcherRegistry;
|
||||
/** Server's NMS/CraftBukkit package */
|
||||
private final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
|
||||
//server package, such as "v1_16_R3"
|
||||
private String serverPackage;
|
||||
/** Server's minor version */
|
||||
@Getter private final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
|
||||
//server minor version such as "16"
|
||||
private int minorVersion;
|
||||
/** Basic universal values */
|
||||
protected Class<?> Packet;
|
||||
protected Class<?> NetworkManager;
|
||||
public Class<?> EntityPlayer;
|
||||
protected Class<?> EntityHuman;
|
||||
protected Class<?> PlayerConnection;
|
||||
|
||||
private Map<String, Class<?>> classes = new HashMap<>();
|
||||
private Map<String, Constructor<?>> constructors = new HashMap<>();
|
||||
private Map<String, Field> fields = new HashMap<>();
|
||||
private Map<String, Method> methods = new HashMap<>();
|
||||
private Map<String, Enum[]> enums = new HashMap<>();
|
||||
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
|
||||
public Field PING;
|
||||
|
||||
/**
|
||||
* Creates new instance, initializes required NMS classes and fields
|
||||
* @throws NoSuchFieldException
|
||||
* @throws SecurityException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws ClassNotFoundException
|
||||
* @throws InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
public NMSStorage() throws NoSuchFieldException, NoSuchMethodException, SecurityException, ClassNotFoundException, IllegalAccessException, InvocationTargetException {
|
||||
serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
classes.put("EnumChatFormat", getNMSClass("net.minecraft.EnumChatFormat", "EnumChatFormat"));
|
||||
classes.put("EntityPlayer", getNMSClass("net.minecraft.server.level.EntityPlayer", "EntityPlayer"));
|
||||
classes.put("Entity", getNMSClass("net.minecraft.world.entity.Entity", "Entity"));
|
||||
classes.put("EntityLiving", getNMSClass("net.minecraft.world.entity.EntityLiving", "EntityLiving"));
|
||||
classes.put("Packet", getNMSClass("net.minecraft.network.protocol.Packet", "Packet"));
|
||||
classes.put("PlayerConnection", getNMSClass("net.minecraft.server.network.PlayerConnection", "PlayerConnection"));
|
||||
classes.put("NetworkManager", getNMSClass("net.minecraft.network.NetworkManager", "NetworkManager"));
|
||||
fields.put("PING", getField(getClass("EntityPlayer"), "ping", "latency", "e"));
|
||||
fields.put("PLAYER_CONNECTION", getFields(getClass("EntityPlayer"), getClass("PlayerConnection")).get(0));
|
||||
methods.put("getHandle", Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle"));
|
||||
methods.put("sendPacket", getMethod(getClass("PlayerConnection"), new String[]{"sendPacket", "func_147359_a"}, getClass("Packet")));
|
||||
if (minorVersion >= 7) {
|
||||
fields.put("NETWORK_MANAGER", getFields(getClass("PlayerConnection"), getClass("NetworkManager")).get(0));
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
fields.put("CHANNEL", getFields(getClass("NetworkManager"), Channel.class).get(0));
|
||||
methods.put("getProfile", getClass("EntityPlayer").getMethod("getProfile"));
|
||||
}
|
||||
initializeChatComponents();
|
||||
initializeChatPacket();
|
||||
initializeDataWatcher();
|
||||
initializeEntitySpawnPacket();
|
||||
initializeEntityTeleportPacket();
|
||||
initializeHeaderFooterPacket();
|
||||
initializeOtherEntityPackets();
|
||||
initializePlayerInfoPacket();
|
||||
initializeScoreboardPackets();
|
||||
initializeEnums();
|
||||
}
|
||||
public Field PLAYER_CONNECTION;
|
||||
public Field NETWORK_MANAGER;
|
||||
public Field CHANNEL;
|
||||
public Method getHandle;
|
||||
public Method sendPacket;
|
||||
public Method getProfile;
|
||||
|
||||
/**
|
||||
* Sets new instance
|
||||
* @param instance - new instance
|
||||
*/
|
||||
public static void setInstance(NMSStorage instance) {
|
||||
NMSStorage.instance = instance;
|
||||
}
|
||||
/** Chat components */
|
||||
protected Class<?> ChatSerializer;
|
||||
public Method ChatSerializer_DESERIALIZE;
|
||||
|
||||
/**
|
||||
* Returns instance
|
||||
* @return instance
|
||||
*/
|
||||
public static NMSStorage getInstance() {
|
||||
return instance;
|
||||
}
|
||||
public final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
|
||||
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
|
||||
|
||||
private void initializeEnums() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException {
|
||||
for (String name : Arrays.asList("ChatMessageType", "EnumPlayerInfoAction", "EnumGamemode", "EnumScoreboardHealthDisplay", "EnumScoreboardAction",
|
||||
"PacketPlayOutScoreboardTeam_a", "EnumChatFormat", "EnumNameTagVisibility", "EnumTeamPush")) {
|
||||
if (classes.containsKey(name)) enums.put(name, (Enum[]) getClass(name).getMethod("values").invoke(null));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeChatComponents() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
|
||||
if (minorVersion >= 7) {
|
||||
classes.put("ChatBaseComponent", getNMSClass("net.minecraft.network.chat.ChatBaseComponent", "ChatBaseComponent"));
|
||||
classes.put("ChatClickable", getNMSClass("net.minecraft.network.chat.ChatClickable", "ChatClickable"));
|
||||
classes.put("ChatComponentText", getNMSClass("net.minecraft.network.chat.ChatComponentText", "ChatComponentText"));
|
||||
classes.put("ChatHoverable", getNMSClass("net.minecraft.network.chat.ChatHoverable", "ChatHoverable"));
|
||||
classes.put("ChatModifier", getNMSClass("net.minecraft.network.chat.ChatModifier", "ChatModifier"));
|
||||
classes.put("ChatSerializer", getNMSClass("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer"));
|
||||
classes.put("EnumClickAction", getNMSClass("net.minecraft.network.chat.ChatClickable$EnumClickAction", "ChatClickable$EnumClickAction", "EnumClickAction"));
|
||||
classes.put("EnumHoverAction", getNMSClass("net.minecraft.network.chat.ChatHoverable$EnumHoverAction", "ChatHoverable$EnumHoverAction", "EnumHoverAction"));
|
||||
classes.put("IChatBaseComponent", getNMSClass("net.minecraft.network.chat.IChatBaseComponent", "IChatBaseComponent"));
|
||||
constructors.put("ChatComponentText", getClass("ChatComponentText").getConstructor(String.class));
|
||||
constructors.put("ChatClickable", getClass("ChatClickable").getConstructor(getClass("EnumClickAction"), String.class));
|
||||
constructors.put("ChatHoverable", getClass("ChatHoverable").getConstructors()[0]);
|
||||
fields.put("ChatBaseComponent_extra", getFields(getClass("ChatBaseComponent"), List.class).get(0));
|
||||
fields.put("ChatBaseComponent_modifier", getFields(getClass("ChatBaseComponent"), getClass("ChatModifier")).get(0));
|
||||
fields.put("ChatComponentText_text", getFields(getClass("ChatComponentText"), String.class).get(0));
|
||||
fields.put("ChatClickable_action", getFields(getClass("ChatClickable"), getClass("EnumClickAction")).get(0));
|
||||
fields.put("ChatClickable_value", getFields(getClass("ChatClickable"), String.class).get(0));
|
||||
fields.put("ChatHoverable_action", getFields(getClass("ChatHoverable"), getClass("EnumHoverAction")).get(0));
|
||||
List<Field> booleans = getFields(getClass("ChatModifier"), Boolean.class);
|
||||
fields.put("ChatModifier_bold", booleans.get(0));
|
||||
fields.put("ChatModifier_italic", booleans.get(1));
|
||||
fields.put("ChatModifier_underlined", booleans.get(2));
|
||||
fields.put("ChatModifier_strikethrough", booleans.get(3));
|
||||
fields.put("ChatModifier_obfuscated", booleans.get(4));
|
||||
fields.put("ChatModifier_clickEvent", getFields(getClass("ChatModifier"), getClass("ChatClickable")).get(0));
|
||||
fields.put("ChatModifier_hoverEvent", getFields(getClass("ChatModifier"), getClass("ChatHoverable")).get(0));
|
||||
methods.put("ChatComponentText_addSibling", getMethod(getClass("ChatComponentText"), new String[]{"addSibling", "a", "func_150257_a"}, getClass("IChatBaseComponent"))); //v1.7.R4+, v1.7.R3-
|
||||
methods.put("ChatSerializer_DESERIALIZE", getMethod(getClass("ChatSerializer"), new String[]{"a", "func_150699_a"}, String.class));
|
||||
methods.put("EnumClickAction_a", getMethod(getClass("EnumClickAction"), new String[]{"a", "func_150672_a"}, String.class));
|
||||
methods.put("EnumHoverAction_a", getMethod(getClass("EnumHoverAction"), new String[]{"a", "func_150684_a"}, String.class));
|
||||
}
|
||||
if (minorVersion >= 16) {
|
||||
classes.put("ChatHexColor", getNMSClass("net.minecraft.network.chat.ChatHexColor", "ChatHexColor"));
|
||||
constructors.put("ChatModifier", getConstructor(getClass("ChatModifier"), 10));
|
||||
List<Field> list = getFields(getClass("ChatHexColor"), String.class);
|
||||
fields.put("ChatHexColor_name", list.get(list.size()-1));
|
||||
fields.put("ChatHexColor_rgb", getFields(getClass("ChatHexColor"), int.class).get(0));
|
||||
fields.put("ChatHoverable_value", getFields(getClass("ChatHoverable"), Object.class).get(0));
|
||||
fields.put("ChatModifier_color", getFields(getClass("ChatModifier"), getClass("ChatHexColor")).get(0));
|
||||
methods.put("ChatHexColor_ofInt", getClass("ChatHexColor").getMethod("a", int.class));
|
||||
methods.put("ChatHexColor_ofString", getClass("ChatHexColor").getMethod("a", String.class));
|
||||
} else if (minorVersion >= 7) {
|
||||
constructors.put("ChatModifier", getClass("ChatModifier").getConstructor());
|
||||
fields.put("ChatHoverable_value", getFields(getClass("ChatHoverable"), getClass("IChatBaseComponent")).get(0));
|
||||
fields.put("ChatModifier_color", getFields(getClass("ChatModifier"), getClass("EnumChatFormat")).get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeChatPacket() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
|
||||
classes.put("PacketPlayOutChat", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutChat", "PacketPlayOutChat", "Packet3Chat"));
|
||||
if (minorVersion >= 12) {
|
||||
classes.put("ChatMessageType", getNMSClass("net.minecraft.network.chat.ChatMessageType", "ChatMessageType"));
|
||||
}
|
||||
if (minorVersion >= 16) {
|
||||
constructors.put("PacketPlayOutChat", getClass("PacketPlayOutChat").getConstructor(getClass("IChatBaseComponent"), getClass("ChatMessageType"), UUID.class));
|
||||
} else if (minorVersion >= 12) {
|
||||
constructors.put("PacketPlayOutChat", getClass("PacketPlayOutChat").getConstructor(getClass("IChatBaseComponent"), getClass("ChatMessageType")));
|
||||
} else if (minorVersion >= 8) {
|
||||
constructors.put("PacketPlayOutChat", getClass("PacketPlayOutChat").getConstructor(getClass("IChatBaseComponent"), byte.class));
|
||||
} else if (minorVersion >= 7){
|
||||
constructors.put("PacketPlayOutChat", getClass("PacketPlayOutChat").getConstructor(getClass("IChatBaseComponent")));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeDataWatcher() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
|
||||
classes.put("DataWatcher", getNMSClass("net.minecraft.network.syncher.DataWatcher", "DataWatcher"));
|
||||
classes.put("DataWatcherItem", getNMSClass("net.minecraft.network.syncher.DataWatcher$Item", "DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject"));
|
||||
if (minorVersion >= 7) {
|
||||
constructors.put("DataWatcher", getClass("DataWatcher").getConstructor(getClass("Entity")));
|
||||
} else {
|
||||
constructors.put("DataWatcher", getClass("DataWatcher").getConstructor());
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
classes.put("DataWatcherObject", getNMSClass("net.minecraft.network.syncher.DataWatcherObject", "DataWatcherObject"));
|
||||
classes.put("DataWatcherRegistry", getNMSClass("net.minecraft.network.syncher.DataWatcherRegistry", "DataWatcherRegistry"));
|
||||
classes.put("DataWatcherSerializer", getNMSClass("net.minecraft.network.syncher.DataWatcherSerializer", "DataWatcherSerializer"));
|
||||
constructors.put("DataWatcherObject", getClass("DataWatcherObject").getConstructor(int.class, getClass("DataWatcherSerializer")));
|
||||
fields.put("DataWatcherItem_TYPE", getFields(getClass("DataWatcherItem"), getClass("DataWatcherObject")).get(0));
|
||||
fields.put("DataWatcherItem_VALUE", getFields(getClass("DataWatcherItem"), Object.class).get(0));
|
||||
fields.put("DataWatcherObject_SLOT", getFields(getClass("DataWatcherObject"), int.class).get(0));
|
||||
fields.put("DataWatcherObject_SERIALIZER", getFields(getClass("DataWatcherObject"), getClass("DataWatcherSerializer")).get(0));
|
||||
methods.put("DataWatcher_REGISTER", getClass("DataWatcher").getMethod("register", getClass("DataWatcherObject"), Object.class));
|
||||
} else {
|
||||
fields.put("DataWatcherItem_TYPE", getFields(getClass("DataWatcherItem"), int.class).get(1));
|
||||
fields.put("DataWatcherItem_VALUE", getFields(getClass("DataWatcherItem"), Object.class).get(0));
|
||||
methods.put("DataWatcher_REGISTER", getMethod(getClass("DataWatcher"), new String[]{"a", "func_75682_a"}, int.class, Object.class));
|
||||
}
|
||||
dataWatcherRegistry = new DataWatcherRegistry(this, getClass("DataWatcherRegistry"));
|
||||
}
|
||||
|
||||
private void initializeEntitySpawnPacket() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
|
||||
classes.put("PacketPlayOutSpawnEntityLiving", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn"));
|
||||
constructors.put("PacketPlayOutSpawnEntityLiving", getClass("PacketPlayOutSpawnEntityLiving").getConstructor(getClass("EntityLiving")));
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_ENTITYID", getFields(getClass("PacketPlayOutSpawnEntityLiving"), int.class).get(0));
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_ENTITYTYPE", getFields(getClass("PacketPlayOutSpawnEntityLiving"), int.class).get(1));
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_YAW", getFields(getClass("PacketPlayOutSpawnEntityLiving"), byte.class).get(0));
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_PITCH", getFields(getClass("PacketPlayOutSpawnEntityLiving"), byte.class).get(0));
|
||||
if (minorVersion >= 9) {
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_UUID", getFields(getClass("PacketPlayOutSpawnEntityLiving"), UUID.class).get(0));
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_X", getFields(getClass("PacketPlayOutSpawnEntityLiving"), double.class).get(0));
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_Y", getFields(getClass("PacketPlayOutSpawnEntityLiving"), double.class).get(1));
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_Z", getFields(getClass("PacketPlayOutSpawnEntityLiving"), double.class).get(2));
|
||||
} else {
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_X", getFields(getClass("PacketPlayOutSpawnEntityLiving"), int.class).get(2));
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_Y", getFields(getClass("PacketPlayOutSpawnEntityLiving"), int.class).get(3));
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_Z", getFields(getClass("PacketPlayOutSpawnEntityLiving"), int.class).get(4));
|
||||
}
|
||||
if (minorVersion <= 14) {
|
||||
fields.put("PacketPlayOutSpawnEntityLiving_DATAWATCHER", getFields(getClass("PacketPlayOutSpawnEntityLiving"), getClass("DataWatcher")).get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeEntityTeleportPacket() throws NoSuchMethodException, SecurityException, ClassNotFoundException {
|
||||
classes.put("PacketPlayOutEntityTeleport", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport"));
|
||||
constructors.put("PacketPlayOutEntityTeleport", getClass("PacketPlayOutEntityTeleport").getConstructor(getClass("Entity")));
|
||||
fields.put("PacketPlayOutEntityTeleport_ENTITYID", getFields(getClass("PacketPlayOutEntityTeleport"), int.class).get(0));
|
||||
fields.put("PacketPlayOutEntityTeleport_YAW", getFields(getClass("PacketPlayOutEntityTeleport"), byte.class).get(0));
|
||||
fields.put("PacketPlayOutEntityTeleport_PITCH", getFields(getClass("PacketPlayOutEntityTeleport"), byte.class).get(1));
|
||||
if (minorVersion >= 9) {
|
||||
fields.put("PacketPlayOutEntityTeleport_X", getFields(getClass("PacketPlayOutEntityTeleport"), double.class).get(0));
|
||||
fields.put("PacketPlayOutEntityTeleport_Y", getFields(getClass("PacketPlayOutEntityTeleport"), double.class).get(1));
|
||||
fields.put("PacketPlayOutEntityTeleport_Z", getFields(getClass("PacketPlayOutEntityTeleport"), double.class).get(2));
|
||||
} else {
|
||||
fields.put("PacketPlayOutEntityTeleport_X", getFields(getClass("PacketPlayOutEntityTeleport"), int.class).get(1));
|
||||
fields.put("PacketPlayOutEntityTeleport_Y", getFields(getClass("PacketPlayOutEntityTeleport"), int.class).get(2));
|
||||
fields.put("PacketPlayOutEntityTeleport_Z", getFields(getClass("PacketPlayOutEntityTeleport"), int.class).get(3));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeHeaderFooterPacket() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
|
||||
if (minorVersion >= 8) {
|
||||
classes.put("PacketPlayOutPlayerListHeaderFooter", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter"));
|
||||
fields.put("PacketPlayOutPlayerListHeaderFooter_HEADER", getFields(getClass("PacketPlayOutPlayerListHeaderFooter"), getClass("IChatBaseComponent")).get(0));
|
||||
fields.put("PacketPlayOutPlayerListHeaderFooter_FOOTER", getFields(getClass("PacketPlayOutPlayerListHeaderFooter"), getClass("IChatBaseComponent")).get(1));
|
||||
if (minorVersion >= 17) {
|
||||
constructors.put("PacketPlayOutPlayerListHeaderFooter", getClass("PacketPlayOutPlayerListHeaderFooter").getConstructor(getClass("IChatBaseComponent"), getClass("IChatBaseComponent")));
|
||||
} else {
|
||||
constructors.put("PacketPlayOutPlayerListHeaderFooter", getClass("PacketPlayOutPlayerListHeaderFooter").getConstructor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeOtherEntityPackets() throws ClassNotFoundException, NoSuchMethodException {
|
||||
classes.put("PacketPlayInUseEntity", getNMSClass("net.minecraft.network.protocol.game.PacketPlayInUseEntity", "PacketPlayInUseEntity", "Packet7UseEntity"));
|
||||
classes.put("PacketPlayOutEntity", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity"));
|
||||
classes.put("PacketPlayOutEntityDestroy", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity"));
|
||||
classes.put("PacketPlayOutEntityLook", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook"));
|
||||
classes.put("PacketPlayOutEntityMetadata", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata"));
|
||||
classes.put("PacketPlayOutNamedEntitySpawn", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn"));
|
||||
constructors.put("PacketPlayOutEntityMetadata", getClass("PacketPlayOutEntityMetadata").getConstructor(int.class, getClass("DataWatcher"), boolean.class));
|
||||
fields.put("PacketPlayOutEntity_ENTITYID", getFields(getClass("PacketPlayOutEntity"), int.class).get(0));
|
||||
fields.put("PacketPlayOutEntityDestroy_ENTITIES", getClass("PacketPlayOutEntityDestroy").getDeclaredFields()[0]);
|
||||
fields.get("PacketPlayOutEntityDestroy_ENTITIES").setAccessible(true);
|
||||
fields.put("PacketPlayOutEntityMetadata_LIST", getFields(getClass("PacketPlayOutEntityMetadata"), List.class).get(0));
|
||||
fields.put("PacketPlayOutNamedEntitySpawn_ENTITYID", getFields(getClass("PacketPlayOutNamedEntitySpawn"), int.class).get(0));
|
||||
if (minorVersion >= 7) {
|
||||
classes.put("EnumEntityUseAction", getNMSClass("net.minecraft.network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction", "PacketPlayInUseEntity$EnumEntityUseAction", "EnumEntityUseAction"));
|
||||
fields.put("PacketPlayInUseEntity_ENTITY", getFields(getClass("PacketPlayInUseEntity"), int.class).get(0));
|
||||
fields.put("PacketPlayInUseEntity_ACTION", getFields(getClass("PacketPlayInUseEntity"), getClass("EnumEntityUseAction")).get(0));
|
||||
}
|
||||
if (minorVersion >= 17) {
|
||||
classes.put("PacketPlayInUseEntity$d", Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$d"));
|
||||
}
|
||||
try {
|
||||
constructors.put("PacketPlayOutEntityDestroy", getClass("PacketPlayOutEntityDestroy").getConstructor(int[].class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
//1.17.0
|
||||
constructors.put("PacketPlayOutEntityDestroy", getClass("PacketPlayOutEntityDestroy").getConstructor(int.class));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializePlayerInfoPacket() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
|
||||
if (minorVersion >= 8) {
|
||||
classes.put("PacketPlayOutPlayerInfo", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo"));
|
||||
classes.put("EnumPlayerInfoAction", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction"));
|
||||
classes.put("PlayerInfoData", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", "PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData"));
|
||||
classes.put("EnumGamemode", getNMSClass("net.minecraft.world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode"));
|
||||
constructors.put("PacketPlayOutPlayerInfo", getClass("PacketPlayOutPlayerInfo").getConstructor(getClass("EnumPlayerInfoAction"), Array.newInstance(getClass("EntityPlayer"), 0).getClass()));
|
||||
constructors.put("PlayerInfoData", getClass("PlayerInfoData").getConstructors()[0]);
|
||||
fields.put("PacketPlayOutPlayerInfo_ACTION", getFields(getClass("PacketPlayOutPlayerInfo"), getClass("EnumPlayerInfoAction")).get(0));
|
||||
fields.put("PacketPlayOutPlayerInfo_PLAYERS", getFields(getClass("PacketPlayOutPlayerInfo"), List.class).get(0));
|
||||
methods.put("PlayerInfoData_getProfile", getClass("PlayerInfoData").getMethod("a"));
|
||||
methods.put("PlayerInfoData_getLatency", getClass("PlayerInfoData").getMethod("b"));
|
||||
methods.put("PlayerInfoData_getGamemode", getClass("PlayerInfoData").getMethod("c"));
|
||||
methods.put("PlayerInfoData_getDisplayName", getClass("PlayerInfoData").getMethod("d"));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeScoreboardPackets() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
|
||||
classes.put("PacketPlayOutScoreboardDisplayObjective", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective", "PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective"));
|
||||
classes.put("PacketPlayOutScoreboardObjective", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective", "PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective"));
|
||||
classes.put("PacketPlayOutScoreboardScore", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore", "PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore"));
|
||||
classes.put("PacketPlayOutScoreboardTeam", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam", "PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam"));
|
||||
classes.put("Scoreboard", getNMSClass("net.minecraft.world.scores.Scoreboard", "Scoreboard"));
|
||||
classes.put("ScoreboardObjective", getNMSClass("net.minecraft.world.scores.ScoreboardObjective", "ScoreboardObjective"));
|
||||
classes.put("ScoreboardTeam", getNMSClass("net.minecraft.world.scores.ScoreboardTeam", "ScoreboardTeam"));
|
||||
classes.put("ScoreboardScore", getNMSClass("net.minecraft.world.scores.ScoreboardScore", "ScoreboardScore"));
|
||||
classes.put("IScoreboardCriteria", getNMSClass("net.minecraft.world.scores.criteria.IScoreboardCriteria", "IScoreboardCriteria"));
|
||||
constructors.put("ScoreboardObjective", getClass("ScoreboardObjective").getConstructors()[0]);
|
||||
constructors.put("Scoreboard", getClass("Scoreboard").getConstructor());
|
||||
constructors.put("ScoreboardTeam", getClass("ScoreboardTeam").getConstructor(getClass("Scoreboard"), String.class));
|
||||
constructors.put("ScoreboardScore", getClass("ScoreboardScore").getConstructor(getClass("Scoreboard"), getClass("ScoreboardObjective"), String.class));
|
||||
constructors.put("PacketPlayOutScoreboardDisplayObjective", getClass("PacketPlayOutScoreboardDisplayObjective").getConstructor(int.class, getClass("ScoreboardObjective")));
|
||||
fields.put("PacketPlayOutScoreboardDisplayObjective_POSITION", getFields(getClass("PacketPlayOutScoreboardDisplayObjective"), int.class).get(0));
|
||||
fields.put("PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME", getFields(getClass("PacketPlayOutScoreboardDisplayObjective"), String.class).get(0));
|
||||
fields.put("PacketPlayOutScoreboardObjective_OBJECTIVENAME", getFields(getClass("PacketPlayOutScoreboardObjective"), String.class).get(0));
|
||||
List<Field> list = getFields(getClass("PacketPlayOutScoreboardObjective"), int.class);
|
||||
fields.put("PacketPlayOutScoreboardObjective_METHOD", list.get(list.size()-1));
|
||||
fields.put("PacketPlayOutScoreboardTeam_NAME", getFields(getClass("PacketPlayOutScoreboardTeam"), String.class).get(0));
|
||||
fields.put("PacketPlayOutScoreboardTeam_PLAYERS", getFields(getClass("PacketPlayOutScoreboardTeam"), Collection.class).get(0));
|
||||
methods.put("ScoreboardTeam_getPlayerNameSet", getMethod(getClass("ScoreboardTeam"), new String[]{"getPlayerNameSet", "func_96670_d"}));
|
||||
methods.put("ScoreboardScore_setScore", getMethod(getClass("ScoreboardScore"), new String[]{"setScore", "func_96647_c"}, int.class));
|
||||
if (minorVersion >= 8) {
|
||||
classes.put("EnumScoreboardHealthDisplay", getNMSClass("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay", "IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay"));
|
||||
classes.put("EnumScoreboardAction", getNMSClass("net.minecraft.server.ScoreboardServer$Action", "ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction"));
|
||||
classes.put("EnumNameTagVisibility", getNMSClass("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility", "ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility"));
|
||||
fields.put("PacketPlayOutScoreboardObjective_RENDERTYPE", getFields(getClass("PacketPlayOutScoreboardObjective"), getClass("EnumScoreboardHealthDisplay")).get(0));
|
||||
methods.put("ScoreboardTeam_setNameTagVisibility", getMethod(getClass("ScoreboardTeam"), new String[]{"setNameTagVisibility", "a"}, getClass("EnumNameTagVisibility")));
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
classes.put("EnumTeamPush", getNMSClass("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush"));
|
||||
methods.put("ScoreboardTeam_setCollisionRule", getClass("ScoreboardTeam").getMethod("setCollisionRule", getClass("EnumTeamPush")));
|
||||
}
|
||||
if (minorVersion >= 13) {
|
||||
constructors.put("PacketPlayOutScoreboardObjective", getClass("PacketPlayOutScoreboardObjective").getConstructor(getClass("ScoreboardObjective"), int.class));
|
||||
constructors.put("PacketPlayOutScoreboardScore_1_13", getClass("PacketPlayOutScoreboardScore").getConstructor(getClass("EnumScoreboardAction"), String.class, String.class, int.class));
|
||||
fields.put("PacketPlayOutScoreboardObjective_DISPLAYNAME", getFields(getClass("PacketPlayOutScoreboardObjective"), getClass("IChatBaseComponent")).get(0));
|
||||
methods.put("ScoreboardTeam_setPrefix", getClass("ScoreboardTeam").getMethod("setPrefix", getClass("IChatBaseComponent")));
|
||||
methods.put("ScoreboardTeam_setSuffix", getClass("ScoreboardTeam").getMethod("setSuffix", getClass("IChatBaseComponent")));
|
||||
methods.put("ScoreboardTeam_setColor", getClass("ScoreboardTeam").getMethod("setColor", getClass("EnumChatFormat")));
|
||||
} else {
|
||||
constructors.put("PacketPlayOutScoreboardObjective", getClass("PacketPlayOutScoreboardObjective").getConstructor());
|
||||
constructors.put("PacketPlayOutScoreboardScore_String", getClass("PacketPlayOutScoreboardScore").getConstructor(String.class));
|
||||
fields.put("PacketPlayOutScoreboardObjective_DISPLAYNAME", getFields(getClass("PacketPlayOutScoreboardObjective"), String.class).get(1));
|
||||
methods.put("ScoreboardTeam_setPrefix", getMethod(getClass("ScoreboardTeam"), new String[]{"setPrefix", "func_96666_b"}, String.class));
|
||||
methods.put("ScoreboardTeam_setSuffix", getMethod(getClass("ScoreboardTeam"), new String[]{"setSuffix", "func_96662_c"}, String.class));
|
||||
if (minorVersion >= 8) {
|
||||
constructors.put("PacketPlayOutScoreboardScore", getClass("PacketPlayOutScoreboardScore").getConstructor(getClass("ScoreboardScore")));
|
||||
} else {
|
||||
constructors.put("PacketPlayOutScoreboardScore", getClass("PacketPlayOutScoreboardScore").getConstructor(getClass("ScoreboardScore"), int.class));
|
||||
}
|
||||
}
|
||||
if (minorVersion >= 17) {
|
||||
classes.put("PacketPlayOutScoreboardTeam_a", Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam$a"));
|
||||
methods.put("PacketPlayOutScoreboardTeam_of", getClass("PacketPlayOutScoreboardTeam").getMethod("a", getClass("ScoreboardTeam")));
|
||||
methods.put("PacketPlayOutScoreboardTeam_ofBoolean", getClass("PacketPlayOutScoreboardTeam").getMethod("a", getClass("ScoreboardTeam"), boolean.class));
|
||||
methods.put("PacketPlayOutScoreboardTeam_ofString", getClass("PacketPlayOutScoreboardTeam").getMethod("a", getClass("ScoreboardTeam"), String.class, getClass("PacketPlayOutScoreboardTeam_a")));
|
||||
} else {
|
||||
constructors.put("PacketPlayOutScoreboardTeam", getClass("PacketPlayOutScoreboardTeam").getConstructor(getClass("ScoreboardTeam"), int.class));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* A helper method that prints all methods of class into console, including their return type, name and parameters
|
||||
* Useful for modded servers which code I can not access
|
||||
* @param clazz - class to show methods of
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private void showMethods(Class<?> clazz) {
|
||||
TAB.getInstance().getPlatform().sendConsoleMessage("--- " + clazz.getSimpleName() + " ---", false);
|
||||
for (Method m : clazz.getMethods()) {
|
||||
TAB.getInstance().getPlatform().sendConsoleMessage(m.getReturnType().getName() + " " + m.getName() + "(" + Arrays.toString(m.getParameterTypes()) + ")", false);
|
||||
}
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class with given potential names in same order
|
||||
* @param names - possible class names
|
||||
* @return class for specified name(s)
|
||||
* @throws ClassNotFoundException if class does not exist
|
||||
*/
|
||||
private Class<?> getNMSClass(String... names) throws ClassNotFoundException {
|
||||
for (String name : names) {
|
||||
try {
|
||||
return getNMSClass(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
|
||||
* @param name - class name
|
||||
* @return class from given name
|
||||
* @throws ClassNotFoundException if class was not found
|
||||
*/
|
||||
private Class<?> getNMSClass(String name) throws ClassNotFoundException {
|
||||
if (minorVersion >= 17) {
|
||||
return Class.forName(name);
|
||||
} else {
|
||||
try {
|
||||
return Class.forName("net.minecraft.server." + serverPackage + "." + name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
//modded server?
|
||||
return Main.class.getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
} catch (NullPointerException e) {
|
||||
//nested class in modded server
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns method with specified possible names and parameters. Throws exception if no such method was found
|
||||
* @param clazz - class to get method from
|
||||
* @param names - possible method names
|
||||
* @param parameterTypes - parameter types of the method
|
||||
* @return method with specified name and parameters
|
||||
* @throws NoSuchMethodException if no such method exists
|
||||
*/
|
||||
private Method getMethod(Class<?> clazz, String[] names, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
for (String name : names) {
|
||||
try {
|
||||
return clazz.getMethod(name, parameterTypes);
|
||||
} catch (Exception e) {
|
||||
//not the first method in array
|
||||
}
|
||||
}
|
||||
throw new NoSuchMethodException("No method found with possible names " + Arrays.toString(names) + " in class " + clazz.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all fields of class with defined class type
|
||||
* @param clazz - class to check fields of
|
||||
* @param type - field type to check for
|
||||
* @return list of all fields with specified class type
|
||||
*/
|
||||
public List<Field> getFields(Class<?> clazz, Class<?> type){
|
||||
if (clazz == null) throw new IllegalArgumentException("Source class cannot be null");
|
||||
List<Field> list = new ArrayList<>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() == type) {
|
||||
field.setAccessible(true);
|
||||
list.add(field);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns field with specified name and makes it accessible
|
||||
* @param clazz - class to get field from
|
||||
* @param name - field name
|
||||
* @return accessible field with defined name
|
||||
* @throws NoSuchFieldException if field was not found
|
||||
*/
|
||||
private Field getField(Class<?> clazz, String name) throws NoSuchFieldException {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (f.getName().equals(name) || (f.getName().split("_").length == 3 && f.getName().split("_")[2].equals(name))) {
|
||||
f.setAccessible(true);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
if (name.equals("ping")){
|
||||
return getField(clazz, "field_71138_i"); //thermos
|
||||
}
|
||||
throw new NoSuchFieldException("Field \"" + name + "\" was not found in class " + clazz.getName());
|
||||
}
|
||||
|
||||
private Constructor<?> getConstructor(Class<?> clazz, int parameterCount) throws NoSuchMethodException {
|
||||
for (Constructor<?> c : clazz.getDeclaredConstructors()) {
|
||||
if (c.getParameterCount() == parameterCount) {
|
||||
c.setAccessible(true);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
throw new NoSuchMethodException("No constructor found in class " + clazz.getName() + " with " + parameterCount + " parameters");
|
||||
}
|
||||
|
||||
private Field getField(Class<?> clazz, String... potentialNames) throws NoSuchFieldException {
|
||||
for (String name : potentialNames) {
|
||||
try {
|
||||
return getField(clazz, name);
|
||||
} catch (NoSuchFieldException e) {
|
||||
//not the first field name from array
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException("No field found in class " + clazz.getName() + " with potential names " + Arrays.toString(potentialNames));
|
||||
}
|
||||
|
||||
public int getMinorVersion() {
|
||||
return minorVersion;
|
||||
}
|
||||
|
||||
public Class<?> getClass(String name){
|
||||
return classes.get(name);
|
||||
}
|
||||
|
||||
public Constructor getConstructor(String name){
|
||||
return constructors.get(name);
|
||||
}
|
||||
|
||||
public Field getField(String name){
|
||||
return fields.get(name);
|
||||
}
|
||||
|
||||
public Method getMethod(String name) {
|
||||
return methods.get(name);
|
||||
}
|
||||
|
||||
public Enum[] getEnum(String name) {
|
||||
return enums.get(name);
|
||||
}
|
||||
|
||||
public void setField(Object obj, String field, Object value) throws IllegalAccessException {
|
||||
fields.get(field).set(obj, value);
|
||||
}
|
||||
|
||||
public DataWatcherRegistry getDataWatcherRegistry() {
|
||||
return dataWatcherRegistry;
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcher class to make work with it much easier
|
||||
*/
|
||||
public class DataWatcher {
|
||||
|
||||
//datawatcher data
|
||||
private Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
|
||||
|
||||
//a helper for easier data write
|
||||
private DataWatcherHelper helper = new DataWatcherHelper(this);
|
||||
|
||||
/**
|
||||
* Sets value into data values
|
||||
* @param type - type of value
|
||||
* @param value - value
|
||||
*/
|
||||
public void setValue(DataWatcherObject type, Object value){
|
||||
dataValues.put(type.getPosition(), new DataWatcherItem(type, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes value by position
|
||||
* @param position - position of value to remove
|
||||
*/
|
||||
public void removeValue(int position) {
|
||||
dataValues.remove(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns item with given position
|
||||
* @param position - position of item
|
||||
* @return item or null if not set
|
||||
*/
|
||||
public DataWatcherItem getItem(int position) {
|
||||
return dataValues.get(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns helper created by this instance
|
||||
* @return data write helper
|
||||
*/
|
||||
public DataWatcherHelper helper() {
|
||||
return helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the class into an instance of NMS.DataWatcher
|
||||
* @return an instance of NMS.DataWatcher with same data
|
||||
* @throws InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
* @throws InstantiationException
|
||||
*/
|
||||
public Object toNMS() throws InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object nmsWatcher;
|
||||
if (nms.getMinorVersion() >= 7) {
|
||||
nmsWatcher = nms.getConstructor("DataWatcher").newInstance(new Object[] {null});
|
||||
} else {
|
||||
nmsWatcher = nms.getConstructor("DataWatcher").newInstance();
|
||||
}
|
||||
for (DataWatcherItem item : dataValues.values()) {
|
||||
Object position;
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
position = nms.getConstructor("DataWatcherObject").newInstance(item.getType().getPosition(), item.getType().getClassType());
|
||||
} else {
|
||||
position = item.getType().getPosition();
|
||||
}
|
||||
nms.getMethod("DataWatcher_REGISTER").invoke(nmsWatcher, position, item.getValue());
|
||||
}
|
||||
return nmsWatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads NMS data watcher and returns and instance of this class with same data
|
||||
* @param nmsWatcher - NMS datawatcher to read
|
||||
* @return an instance of this class with same values
|
||||
* @throws SecurityException
|
||||
* @throws NoSuchMethodException
|
||||
* @throws InvocationTargetException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static DataWatcher fromNMS(Object nmsWatcher) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException{
|
||||
DataWatcher watcher = new DataWatcher();
|
||||
List<Object> items = (List<Object>) nmsWatcher.getClass().getMethod("c").invoke(nmsWatcher);
|
||||
if (items != null) {
|
||||
for (Object watchableObject : items) {
|
||||
DataWatcherItem w = DataWatcherItem.fromNMS(watchableObject);
|
||||
watcher.setValue(w.getType(), w.getValue());
|
||||
}
|
||||
}
|
||||
return watcher;
|
||||
}
|
||||
}
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitPacketBuilder;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.packets.IChatBaseComponent;
|
||||
|
||||
/**
|
||||
* A class to help assigning DataWatcher items as positions often change per-version
|
||||
*/
|
||||
public class DataWatcherHelper {
|
||||
|
||||
//position of armor stand flags
|
||||
private final int armorStandFlagsPosition = getArmorStandFlagsPosition();
|
||||
|
||||
//original datawatcher to write to
|
||||
private DataWatcher data;
|
||||
|
||||
//data watcher registry
|
||||
private DataWatcherRegistry registry;
|
||||
|
||||
/**
|
||||
* Constructs new instance of this class with given parent
|
||||
* @param data - data to write to
|
||||
*/
|
||||
public DataWatcherHelper(DataWatcher data) {
|
||||
this.data = data;
|
||||
this.registry = NMSStorage.getInstance().getDataWatcherRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns armor stand flags position based on server version
|
||||
* @return armor stand flags position based on server version
|
||||
*/
|
||||
private int getArmorStandFlagsPosition() {
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 17) {
|
||||
//1.17.x
|
||||
return 15;
|
||||
} else if (NMSStorage.getInstance().getMinorVersion() >= 15) {
|
||||
//1.15.x, 1.16.x
|
||||
return 14;
|
||||
} else if (NMSStorage.getInstance().getMinorVersion() >= 14) {
|
||||
//1.14.x
|
||||
return 13;
|
||||
} else if (NMSStorage.getInstance().getMinorVersion() >= 10) {
|
||||
//1.10.x - 1.13.x
|
||||
return 11;
|
||||
} else {
|
||||
//1.8.1 - 1.9.x
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity byte flags
|
||||
* @param flags - flags to write
|
||||
*/
|
||||
public void setEntityFlags(byte flags) {
|
||||
data.setValue(new DataWatcherObject(0, registry.getByte()), 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(String customName, ProtocolVersion clientVersion) {
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 13) {
|
||||
try {
|
||||
data.setValue(new DataWatcherObject(2, registry.getOptionalComponent()), Optional.ofNullable(((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to create component", e);
|
||||
}
|
||||
} else if (NMSStorage.getInstance().getMinorVersion() >= 8){
|
||||
data.setValue(new DataWatcherObject(2, registry.getString()), customName);
|
||||
} else {
|
||||
//name length is limited to 64 characters on <1.8
|
||||
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 6){
|
||||
data.setValue(new DataWatcherObject(10, registry.getString()), cutName);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(5, registry.getString()), cutName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes custom name visibility boolean
|
||||
* @param visible - if visible or not
|
||||
*/
|
||||
public void setCustomNameVisible(boolean visible) {
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
|
||||
data.setValue(new DataWatcherObject(3, registry.getBoolean()), visible);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(3, registry.getByte()), (byte)(visible?1:0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity health
|
||||
* @param health - health of entity
|
||||
*/
|
||||
public void setHealth(float health) {
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 6) {
|
||||
data.setValue(new DataWatcherObject(6, registry.getFloat()), health);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(16, registry.getInteger()), (int)health);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes armor stand flags
|
||||
* @param flags - flags to write
|
||||
*/
|
||||
public void setArmorStandFlags(byte flags) {
|
||||
data.setValue(new DataWatcherObject(armorStandFlagsPosition, registry.getByte()), flags);
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
|
||||
public class DataWatcherItem {
|
||||
|
||||
//type of value (position + data type (1.9+))
|
||||
private DataWatcherObject type;
|
||||
|
||||
//actual data value
|
||||
private Object value;
|
||||
|
||||
/**
|
||||
* Constructs new instance of the object with given parameters
|
||||
* @param type - value type
|
||||
* @param value - value
|
||||
*/
|
||||
public DataWatcherItem(DataWatcherObject type, Object value){
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and instance of this class from given NMS item
|
||||
* @param nmsItem - NMS item
|
||||
* @return instance of this class with same data
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
public static DataWatcherItem fromNMS(Object nmsItem) throws IllegalAccessException {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
|
||||
Object nmsObject = nms.getField("DataWatcherItem_TYPE").get(nmsItem);
|
||||
return new DataWatcherItem(new DataWatcherObject(nms.getField("DataWatcherObject_SLOT").getInt(nmsObject), nms.getField("DataWatcherObject_SERIALIZER").get(nmsObject)), nms.getField("DataWatcherItem_VALUE").get(nmsItem));
|
||||
} else {
|
||||
return new DataWatcherItem(new DataWatcherObject(nms.getField("DataWatcherItem_TYPE").getInt(nmsItem), null), nms.getField("DataWatcherItem_VALUE").get(nmsItem));
|
||||
}
|
||||
}
|
||||
|
||||
public DataWatcherObject getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcherObject class to make work with it much easier
|
||||
*/
|
||||
public class DataWatcherObject {
|
||||
|
||||
//position in datawatcher
|
||||
private int position;
|
||||
|
||||
//value class type used since 1.9
|
||||
private Object classType;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of this class with given parameters
|
||||
* @param position - position in datawatcher
|
||||
* @param classType - value class type
|
||||
*/
|
||||
public DataWatcherObject(int position, Object classType){
|
||||
this.position = position;
|
||||
this.classType = classType;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public Object getClassType() {
|
||||
return classType;
|
||||
}
|
||||
}
|
||||
-85
@@ -1,85 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcherRegistry class to make work with it much easier
|
||||
*/
|
||||
public class DataWatcherRegistry {
|
||||
|
||||
//used registry types
|
||||
private Object registryByte;
|
||||
private Object registryInteger;
|
||||
private Object registryFloat;
|
||||
private Object registryString;
|
||||
private Object registryOptionalComponent;
|
||||
private Object registryBoolean;
|
||||
|
||||
/**
|
||||
* Initializes required NMS classes and fields
|
||||
*/
|
||||
public DataWatcherRegistry(NMSStorage nms, Class<?> registry) {
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
Map<String, Object> fields = getStaticFields(registry);
|
||||
registryByte = fields.get("a");
|
||||
registryInteger = fields.get("b");
|
||||
registryFloat = fields.get("c");
|
||||
registryString = fields.get("d");
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
registryOptionalComponent = fields.get("f");
|
||||
registryBoolean = fields.get("i");
|
||||
} else {
|
||||
registryBoolean = fields.get("h");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets values of all static fields in a class
|
||||
* @param clazz class to return field values from
|
||||
* @return map of values
|
||||
*/
|
||||
private Map<String, Object> getStaticFields(Class<?> clazz){
|
||||
Map<String, Object> fields = new HashMap<>();
|
||||
if (clazz == null) return fields;
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (Modifier.isStatic(field.getModifiers())) {
|
||||
try {
|
||||
fields.put(field.getName(), field.get(null));
|
||||
} catch (Exception e) {
|
||||
//this will never happen
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
public Object getBoolean() {
|
||||
return registryBoolean;
|
||||
}
|
||||
|
||||
public Object getByte() {
|
||||
return registryByte;
|
||||
}
|
||||
|
||||
public Object getFloat() {
|
||||
return registryFloat;
|
||||
}
|
||||
|
||||
public Object getInteger() {
|
||||
return registryInteger;
|
||||
}
|
||||
|
||||
public Object getString() {
|
||||
return registryString;
|
||||
}
|
||||
|
||||
public Object getOptionalComponent() {
|
||||
return registryOptionalComponent;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.permission;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.permission.PermissionPlugin;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
|
||||
/**
|
||||
* Vault permission hook
|
||||
*/
|
||||
public class Vault implements PermissionPlugin {
|
||||
|
||||
//permission plugi
|
||||
private Permission permission;
|
||||
|
||||
//vault version
|
||||
private String vaultVersion;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param permission permission plugin implementation
|
||||
* @param vaultVersion vault version
|
||||
*/
|
||||
public Vault(Permission permission, String vaultVersion) {
|
||||
this.permission = permission;
|
||||
this.vaultVersion = vaultVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrimaryGroup(TabPlayer p) {
|
||||
if (getName().equals("SuperPerms")) return "<null>";
|
||||
return permission.getPrimaryGroup((Player) p.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getAllGroups(TabPlayer p) {
|
||||
if (getName().equals("SuperPerms")) return new String[] {"<null>"};
|
||||
return permission.getPlayerGroups((Player) p.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return permission.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "Vault " + vaultVersion;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
# Detailed explanation of all options available at https://github.com/NEZNAMY/TAB/wiki/config.yml
|
||||
|
||||
#changing nametags, affecting sorting, collision rule
|
||||
change-nametag-prefix-suffix: true
|
||||
|
||||
#prefix/suffix in tablist
|
||||
change-tablist-prefix-suffix: true
|
||||
|
||||
#header/footer
|
||||
enable-header-footer: true
|
||||
|
||||
header:
|
||||
- "&3&lServer name"
|
||||
- "&7&l>> %animation:Welcome%&3 &l%player%&7&l! &7&l<<"
|
||||
- "&7Online players: &f%online%"
|
||||
- "&6Online staff: &e%staffonline%"
|
||||
- "%animation:ping-tps%"
|
||||
- ""
|
||||
- "&7&l Used memory: %memory-used% MB / %memory-max% MB"
|
||||
- "%animation:MyAnimation1%"
|
||||
footer:
|
||||
- "%animation:MyAnimation1%"
|
||||
- "%animation:time%"
|
||||
- ""
|
||||
- "&7Visit our webpage %animation:web%"
|
||||
|
||||
#supports any numeric placeholder, set to "" to disable the feature
|
||||
yellow-number-in-tablist: "%ping%"
|
||||
|
||||
group-sorting-priority-list:
|
||||
- owner
|
||||
- admin
|
||||
- mod
|
||||
- helper
|
||||
- builder
|
||||
- premium
|
||||
- player
|
||||
- default
|
||||
|
||||
Groups:
|
||||
Owner:
|
||||
tabprefix: "&0&l[&a&lOwner&0&l] &a"
|
||||
tagprefix: "&2&lOwner &a"
|
||||
Admin:
|
||||
tabprefix: "&0&l[&4&lAdmin&0&l] &1"
|
||||
tagprefix: "%animation:admin_prefix%"
|
||||
Player:
|
||||
tabprefix: "&0&l[&7&lPlayer&0&l] &3"
|
||||
tagprefix: "&2&lPlayer &3"
|
||||
example_group:
|
||||
header:
|
||||
- "This is an example of per-group header/footer"
|
||||
footer:
|
||||
- "applied to a group"
|
||||
|
||||
#default settings for all groups, all groups will take properties from this section unless player's primary group overrides a specific setting
|
||||
_OTHER_:
|
||||
tabprefix: "%vault-prefix%"
|
||||
tagprefix: "%vault-prefix%"
|
||||
tabsuffix: "%afk%"
|
||||
tagsuffix: "%afk%"
|
||||
customtabname: "%essentialsnick%"
|
||||
customtagname: "%essentialsnick%"
|
||||
|
||||
Users:
|
||||
_NEZNAMY_:
|
||||
tabprefix: "&6&lTAB Author &a"
|
||||
tagprefix: "&6&lTAB &r&a"
|
||||
abovename: "&bOh look, 3 lines of text !"
|
||||
belowname: "&eThere's only one plugin out there supporting it"
|
||||
|
||||
#additional very limited line of text below nametag
|
||||
#don't forget this enables on all player entities, which includes NPCs
|
||||
#tags so you can find this section using the mighty ctrl + f feature: [NPC, Citizens, below_name, health]
|
||||
classic-vanilla-belowname:
|
||||
|
||||
#enabling the feature
|
||||
enabled: true
|
||||
|
||||
#number or numeric placeholder, supports per-player placeholders
|
||||
number: "%health%"
|
||||
|
||||
#text after number, must be same on all players
|
||||
text: "Health"
|
||||
|
||||
#controlling the collision rule
|
||||
enable-collision: true
|
||||
|
||||
#using the opposite of the setting above for worlds
|
||||
revert-collision-rule-in-worlds:
|
||||
- revertedworld
|
||||
|
||||
#preventing players with spectator gamemode from being moved to the bottom of tablist with transparent name
|
||||
do-not-move-spectators: false
|
||||
|
||||
#allows players with tab.spectatorbypass permission to see spectators moved despite the option above enabled
|
||||
allow-spectator-bypass-permission: false
|
||||
|
||||
#using armor stands for nametags, bypassing all limits (they are not on server sided & asynchronous - no TPS loss)
|
||||
unlimited-nametag-prefix-suffix-mode:
|
||||
|
||||
#enabling the feature
|
||||
enabled: false
|
||||
|
||||
#using a feature introduced in 1.8.1 to make hitbox zero so you can't cry it affects pvp despite hit redirection
|
||||
#1.8.0 clients will see wild things
|
||||
#automatically enabled for 1.9+
|
||||
use-marker-tag-for-1-8-x-clients: false
|
||||
|
||||
#disabling the feature for players riding boats to prevent massive desync
|
||||
disable-on-boats: true
|
||||
|
||||
#in blocks
|
||||
space-between-lines: 0.22
|
||||
|
||||
per-world-settings:
|
||||
world1:
|
||||
header:
|
||||
- "an example of world with custom"
|
||||
footer:
|
||||
- "header/footer and prefix/suffix"
|
||||
Groups:
|
||||
Owner:
|
||||
tabprefix: "&0&l[&a&lOwner&0&l] &a"
|
||||
tagprefix: "&2&lOwner &a"
|
||||
|
||||
#separating multiple worlds with "-"
|
||||
world2-world3:
|
||||
header:
|
||||
- "This is a shared header for"
|
||||
- "world2 and world3"
|
||||
|
||||
placeholders:
|
||||
#output of %afk%
|
||||
afk-yes: " &4*&4&lAFK&4*&r"
|
||||
afk-no: ""
|
||||
|
||||
#%date%
|
||||
date-format: "dd.MM.yyyy"
|
||||
|
||||
#%time%
|
||||
time-format: "[HH:mm:ss / h:mm a]"
|
||||
|
||||
#in hours, in case of wrong timezone
|
||||
time-offset: 0
|
||||
|
||||
#don't forget color codes
|
||||
remove-strings:
|
||||
- "[]"
|
||||
- "< >"
|
||||
|
||||
#aliases for %rank%, this is not the place where group prefix is defined!
|
||||
rank-aliases:
|
||||
Admin: "&4&lADMIN"
|
||||
Mod: "&b&lMOD"
|
||||
Premium: "&6&lPREMIUM"
|
||||
Ultra: "&b&lULTRA"
|
||||
Legend: "&a&lLEGEND"
|
||||
Titan: "&c&lTITAN"
|
||||
Youtuber: "&c&lYOUTUBE"
|
||||
_OTHER_: "&7No Rank"
|
||||
|
||||
disable-features-in-worlds:
|
||||
header-footer:
|
||||
- disabledworld
|
||||
tablist-names:
|
||||
- disabledworld
|
||||
nametag:
|
||||
- disabledworld
|
||||
yellow-number:
|
||||
- disabledworld
|
||||
bossbar:
|
||||
- disabledworld
|
||||
belowname:
|
||||
- disabledworld
|
||||
unlimited-nametags:
|
||||
- disabledworld
|
||||
|
||||
placeholderapi-refresh-intervals:
|
||||
default-refresh-interval: 500
|
||||
server:
|
||||
"%server_uptime%": 1000
|
||||
"%server_tps_1_colored%": 1000
|
||||
player:
|
||||
"%player_health%": 200
|
||||
"%player_ping%": 1000
|
||||
"%vault_prefix%": 1000
|
||||
relational:
|
||||
"%rel_factionsuuid_relation_color%": 500
|
||||
|
||||
#different playerlist in different worlds
|
||||
per-world-playerlist:
|
||||
|
||||
#to enable the feature
|
||||
enabled: false
|
||||
|
||||
#allows players with tab.bypass permission to see everyone in tablist despite this option enabled
|
||||
allow-bypass-permission: false
|
||||
|
||||
#players in these worlds will see everyone despite the feature enabled
|
||||
ignore-effect-in-worlds:
|
||||
- ignoredworld
|
||||
- build
|
||||
|
||||
#groups of worlds to share playerlist
|
||||
shared-playerlist-world-groups:
|
||||
#group name, can be anything
|
||||
lobby:
|
||||
- lobby1
|
||||
- lobby2
|
||||
minigames:
|
||||
- paintball
|
||||
- bedwars
|
||||
|
||||
#permission node is tab.sort.<permission group from sorting list>
|
||||
sort-players-by-permissions: false
|
||||
|
||||
#making tamed animals no longer copy nametag properties from their owner (prefix/suffix/visibility)
|
||||
fix-pet-names: false
|
||||
|
||||
#taking primary group from permission plugin
|
||||
use-primary-group: true
|
||||
|
||||
#if the option above is false, all groups are taken from permission plugin and the one higher in this list is used as primary
|
||||
primary-group-finding-list:
|
||||
- Owner
|
||||
- Admin
|
||||
- Helper
|
||||
- default
|
||||
|
||||
#if none of the above fits your needs, you can enable this to give groups using tab.group.<group name from the list above> permission
|
||||
assign-groups-by-permissions: false
|
||||
|
||||
#preventing other plugins from overriding TAB
|
||||
anti-override:
|
||||
scoreboard-teams: true
|
||||
scoreboard-objectives: true
|
||||
tablist-names: true
|
||||
usernames: true
|
||||
|
||||
#permanently invisible nametags
|
||||
invisible-nametags: false
|
||||
|
||||
#faking ping of players
|
||||
ping-spoof:
|
||||
enabled: false
|
||||
value: 0
|
||||
|
||||
#unlocks extra console messages
|
||||
debug: false
|
||||
Loaded 100 of 514 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user