Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b38954694 | ||
|
|
9eb350df30 | ||
|
|
9070a6aadc | ||
|
|
c68e135c66 | ||
|
|
8693b4fe8f | ||
|
|
cb394007a4 | ||
|
|
cd92910281 | ||
|
|
5f77dd0adb | ||
|
|
4ef94d3c60 | ||
|
|
8e9b575c37 | ||
|
|
beab7d5a4f | ||
|
|
d2f5fc4d69 | ||
|
|
fb9a7f693b | ||
|
|
1d2d636270 | ||
|
|
37ec5ee442 | ||
|
|
5ed67cf5f8 | ||
|
|
76fc47d5bd | ||
|
|
aa856beee1 | ||
|
|
e6f2cc3eda | ||
|
|
02fd45587b | ||
|
|
7cef6eeb61 | ||
|
|
da919bc43b | ||
|
|
2b661d6c5b | ||
|
|
ee0ced63ef | ||
|
|
736dad5819 | ||
|
|
11f36e8a22 | ||
|
|
2cddf0b313 | ||
|
|
36a2d449b7 | ||
|
|
b6786456a2 | ||
|
|
76e8e1af6f | ||
|
|
ff55838a54 | ||
|
|
b14b6828cc | ||
|
|
be7fb67988 | ||
|
|
ae0be45d7d | ||
|
|
ba237d5dc4 | ||
|
|
bdf5cd8751 | ||
|
|
9a6d309840 | ||
|
|
9a6e92a58c | ||
|
|
1bc018918b | ||
|
|
f32f8f5cdd | ||
|
|
39585de3f0 | ||
|
|
9ce81fc68e | ||
|
|
0d56f0da98 | ||
|
|
14a3d8da12 | ||
|
|
e4fa99b865 | ||
|
|
96af2cb573 | ||
|
|
f53ec58b2f | ||
|
|
2ff30dda83 | ||
|
|
c0972f8faf | ||
|
|
9663e1aa7e | ||
|
|
d83a61b30a | ||
|
|
96d83da9b7 | ||
|
|
f1d881e0ba | ||
|
|
d1285c7e04 | ||
|
|
d4476e2d4c | ||
|
|
3124d51381 | ||
|
|
b6ae1a79d4 | ||
|
|
42d7b598bf | ||
|
|
052e828d5a | ||
|
|
55d4f8da6b | ||
|
|
1c5a4bbacc | ||
|
|
565d4bdeec | ||
|
|
3d9ef7d252 | ||
|
|
161bcbb236 | ||
|
|
c8af0ab5bc | ||
|
|
98e9c167c5 | ||
|
|
ed815c5767 | ||
|
|
f6e53ac91f | ||
|
|
3e5df50f9e | ||
|
|
1e28a93c5f | ||
|
|
717bdd3550 | ||
|
|
3bee514bb9 | ||
|
|
678d4b8db7 | ||
|
|
d76e5e7f9b | ||
|
|
9629192305 | ||
|
|
eef7debdba | ||
|
|
138d9f7f46 | ||
|
|
e4abd9ec2f | ||
|
|
8efce72e26 | ||
|
|
15ebca2d11 | ||
|
|
6583489523 | ||
|
|
5610d52813 | ||
|
|
618b61dd60 | ||
|
|
2de58f7a3b | ||
|
|
2bd94158f9 | ||
|
|
4e7ab43ac9 | ||
|
|
33529d0cfd | ||
|
|
a5b929bdf5 | ||
|
|
d10bd3358d | ||
|
|
ffe928fbe8 | ||
|
|
fd61739d96 | ||
|
|
e1602a5655 | ||
|
|
a936302fd2 | ||
|
|
7f0d4df6ca | ||
|
|
35ab0c67c5 | ||
|
|
090633140f | ||
|
|
8efe02c2ef | ||
|
|
2dbcaf029c | ||
|
|
ce758534bd | ||
|
|
15caf70170 | ||
|
|
c07c3a3a4f | ||
|
|
b9000af55e | ||
|
|
ed15ba29bb | ||
|
|
2fb8da022a | ||
|
|
ed65088cb8 | ||
|
|
f367542096 | ||
|
|
00e457542d | ||
|
|
b0c423b2dd | ||
|
|
eb5e3d637c | ||
|
|
4acd24b829 | ||
|
|
f8576834fe | ||
|
|
1e9888a8b5 | ||
|
|
d99b6643e9 | ||
|
|
0b98d98867 | ||
|
|
d040b0faf5 | ||
|
|
d53c20da56 | ||
|
|
fad035699f | ||
|
|
c7ab2b93a3 | ||
|
|
4b75144276 | ||
|
|
99edcf68c1 | ||
|
|
9b6f1ce955 | ||
|
|
b570020944 | ||
|
|
e91ba4ef79 | ||
|
|
a1fbefb13f | ||
|
|
340592d951 | ||
|
|
a1c0bf7dec | ||
|
|
a62f20469e | ||
|
|
3f1f78315b | ||
|
|
12d8bc75cd | ||
|
|
c1cf02ee9f | ||
|
|
7ac240af76 | ||
|
|
82c34a33b5 | ||
|
|
d2330337ef | ||
|
|
af38bd0383 | ||
|
|
c0a9ede377 | ||
|
|
555ba3eb73 | ||
|
|
6f0e757ad6 | ||
|
|
077b8fee96 | ||
|
|
643df47437 | ||
|
|
9e1ea4678d | ||
|
|
f3576b678b | ||
|
|
2b49bce998 | ||
|
|
896048ffff | ||
|
|
04cb14f790 | ||
|
|
bd22b0c08b | ||
|
|
95ce52bbeb | ||
|
|
6b411f5cce | ||
|
|
c15b0d178b | ||
|
|
f5ffb69675 | ||
|
|
24e59d970f | ||
|
|
4ece4b66cd | ||
|
|
7852d244bb | ||
|
|
d1c2487ba4 | ||
|
|
4ea789efcf | ||
|
|
43fc97c671 | ||
|
|
a20378d5cb | ||
|
|
7c0384c96b | ||
|
|
7d4419b300 | ||
|
|
393af9b5c5 | ||
|
|
559e423a1d | ||
|
|
a2d68e14e6 | ||
|
|
580b67d9ed | ||
|
|
080654f703 | ||
|
|
002a002749 | ||
|
|
7e888e46f9 | ||
|
|
199ca94acd | ||
|
|
67455bd47d | ||
|
|
fbb0e252c5 | ||
|
|
2566972ac7 | ||
|
|
9455294060 | ||
|
|
6870b42a1a | ||
|
|
32cd414a51 | ||
|
|
35f63b7075 | ||
|
|
f9097b11b6 | ||
|
|
dc5acdf729 | ||
|
|
e5fb25daee | ||
|
|
33b6867c02 | ||
|
|
d572973b60 | ||
|
|
3d240f0f7d | ||
|
|
3a4973d760 | ||
|
|
811c9716a2 | ||
|
|
771f6db89a | ||
|
|
97f515f573 | ||
|
|
c589dfb281 | ||
|
|
82efe5b4d1 | ||
|
|
765f593b88 | ||
|
|
21e5de25cb | ||
|
|
4333e44f6d | ||
|
|
7070fd886b | ||
|
|
89480a9018 | ||
|
|
19ce873037 | ||
|
|
977007b427 | ||
|
|
218c1c9d9a | ||
|
|
f358ac9d9c | ||
|
|
58fe2b4582 | ||
|
|
211624b1a9 | ||
|
|
6874369a48 | ||
|
|
f8c52d3577 | ||
|
|
376bf5314d | ||
|
|
2346df67a0 | ||
|
|
e63c7b43e1 | ||
|
|
578691fe8c | ||
|
|
189d8a82d5 | ||
|
|
9412d9cc28 | ||
|
|
75b2bfa14c | ||
|
|
7fcb5c8aa3 | ||
|
|
9827711111 | ||
|
|
16aecd65e6 | ||
|
|
c143ecc6ae | ||
|
|
2935f9e205 | ||
|
|
a6a33a79d4 | ||
|
|
82f9d8ddee | ||
|
|
daa6012b36 | ||
|
|
5001c82f23 | ||
|
|
59dbad9abf | ||
|
|
945c7cc7b8 | ||
|
|
3391f7646c | ||
|
|
88070aa2b2 | ||
|
|
c792296746 | ||
|
|
18a13f777d | ||
|
|
469529b324 | ||
|
|
4715a5ab9d | ||
|
|
be305162a6 | ||
|
|
7a1bc16e9d | ||
|
|
eb2c80deb8 | ||
|
|
0863b77bf0 | ||
|
|
769a698509 | ||
|
|
53870b2c81 | ||
|
|
e198a5df88 | ||
|
|
ac947f698b | ||
|
|
ecabd62b6b | ||
|
|
f350d200ac | ||
|
|
97d5a3f7ef | ||
|
|
f15f3efc3f | ||
|
|
7563b6b946 | ||
|
|
421c53e6be | ||
|
|
3db8666951 | ||
|
|
ecc6e01cf9 | ||
|
|
becf81db00 | ||
|
|
92f5b337c9 | ||
|
|
3ddf884c23 | ||
|
|
116117af55 | ||
|
|
0980bcfca5 | ||
|
|
20e2e8ded6 | ||
|
|
13af0b7b2b | ||
|
|
cbcbfdcbf1 | ||
|
|
38eb294667 | ||
|
|
71d16ff906 | ||
|
|
395058d771 | ||
|
|
d8af7c454c | ||
|
|
2f10084e7a | ||
|
|
5af387b32d | ||
|
|
4b7144f705 | ||
|
|
0fbbbb6485 | ||
|
|
f033d78785 | ||
|
|
2ea79eb57d | ||
|
|
226ee0db9d | ||
|
|
8a62f21e84 | ||
|
|
c353c39086 | ||
|
|
2b2c877ab2 | ||
|
|
016af4bfee | ||
|
|
b3d26d91de | ||
|
|
a7a7f409eb | ||
|
|
f44ccec712 | ||
|
|
1c0887733c | ||
|
|
7ee3c35785 | ||
|
|
01b725cf38 | ||
|
|
1ac6d90633 | ||
|
|
9704193237 | ||
|
|
69b150b391 | ||
|
|
7ffb3edda5 | ||
|
|
046eb68ce6 | ||
|
|
816d80a27c | ||
|
|
957037e5f4 | ||
|
|
9ca179a71f | ||
|
|
cb4d96f1ba | ||
|
|
7d57522775 | ||
|
|
902565f1b1 | ||
|
|
2f8de436af | ||
|
|
38bd035d99 | ||
|
|
88b6d1a3ca | ||
|
|
a74878e7bb | ||
|
|
2b6eba7c4d | ||
|
|
0de1b86ef5 | ||
|
|
ae0ab6471b | ||
|
|
c24cdaa8a4 | ||
|
|
df1442dd8a | ||
|
|
b17d5306db | ||
|
|
88d5294b8e | ||
|
|
8c3405d01f | ||
|
|
9f83ba700c | ||
|
|
ae147211ca | ||
|
|
01d305eba0 | ||
|
|
08c30574a9 | ||
|
|
39c4725a2f | ||
|
|
7002aeefaf | ||
|
|
f1201cc5bc | ||
|
|
0b99d4ac52 | ||
|
|
642cb701dd | ||
|
|
66d6aee115 | ||
|
|
38d8eb1108 | ||
|
|
f9941c5dff | ||
|
|
855a6f447a | ||
|
|
fa0a1f8553 | ||
|
|
0d1fee1636 | ||
|
|
ec8269332f | ||
|
|
00fe9400e6 | ||
|
|
8389b1e151 | ||
|
|
2289d10d6c | ||
|
|
b30af4f144 | ||
|
|
041322c53f | ||
|
|
548968bcfc | ||
|
|
6d405b70e7 | ||
|
|
1b4d4acc63 | ||
|
|
6f498089fb | ||
|
|
8e27cba0f6 | ||
|
|
ae48041a96 | ||
|
|
bad16a4cdc | ||
|
|
07baf769dd | ||
|
|
ab409643f6 | ||
|
|
c745a61601 | ||
|
|
c4e8e83d1e | ||
|
|
9133f3d792 | ||
|
|
b779d584a5 | ||
|
|
05bb61b3ab | ||
|
|
5d2885e662 | ||
|
|
ef645c1058 | ||
|
|
52a476fd0a | ||
|
|
7386c45f06 | ||
|
|
1a508ddedf | ||
|
|
a8675f254a | ||
|
|
0f70cd789e | ||
|
|
b8f594f42c | ||
|
|
7857527ce1 | ||
|
|
05c41a5469 | ||
|
|
f11f8afdc4 | ||
|
|
fa499aa46d | ||
|
|
e8a7cc026f | ||
|
|
433ef6a8a7 | ||
|
|
fc4b5a7bcd | ||
|
|
bd43134d58 | ||
|
|
557970ba4e | ||
|
|
d3418e0067 | ||
|
|
b8d1845d4e | ||
|
|
32e360409d | ||
|
|
c54a778af9 | ||
|
|
ce25698f87 | ||
|
|
4d95cc838a | ||
|
|
67fa0a231e | ||
|
|
3c2e8c81c4 | ||
|
|
484d46252b | ||
|
|
a2e3c67298 | ||
|
|
b2ea05d8dd | ||
|
|
1477f47962 | ||
|
|
273ce49c14 | ||
|
|
96b6790646 | ||
|
|
38227ec878 | ||
|
|
11da632972 | ||
|
|
3721b9fe22 | ||
|
|
ecb697e5fb | ||
|
|
c754772280 | ||
|
|
5f2b4ba0b3 | ||
|
|
0ab60becfc | ||
|
|
1dcfb7d6dc | ||
|
|
3c0b44b365 | ||
|
|
ec4aec50ef | ||
|
|
9c86cfe056 | ||
|
|
6bb2337276 | ||
|
|
4702ca255b | ||
|
|
ab47aa36cc | ||
|
|
4bb14c4a4e | ||
|
|
1dd75162f9 | ||
|
|
a2242e3c27 | ||
|
|
26ecf494a9 | ||
|
|
659af5d0b3 | ||
|
|
382a5ee7fc | ||
|
|
7364496844 | ||
|
|
d342316b87 | ||
|
|
b38141da88 | ||
|
|
98f80f7dae | ||
|
|
c134ec6bfe | ||
|
|
4e3f759243 | ||
|
|
a5f2e0157b | ||
|
|
fd21032e51 | ||
|
|
c4f5f9b7d3 | ||
|
|
6baf5a7068 | ||
|
|
d3460174de | ||
|
|
f9f19628ca | ||
|
|
04e48fea5d | ||
|
|
3e99e22fce | ||
|
|
0b244d5ff7 | ||
|
|
703f2ff316 | ||
|
|
34a2bbdbae | ||
|
|
cd8697c1a4 | ||
|
|
ed2dc2dd51 | ||
|
|
2dfdc1b993 | ||
|
|
6f2d7f1194 | ||
|
|
30009ef127 | ||
|
|
051a422331 | ||
|
|
fab6489e9c | ||
|
|
a448614e4a | ||
|
|
84ad874b2a | ||
|
|
19ba978291 | ||
|
|
e304469e55 | ||
|
|
7ce9e2592a | ||
|
|
681b812c7b | ||
|
|
ee1ab6785d | ||
|
|
57704b6622 | ||
|
|
7fe804e0fc | ||
|
|
a71529b641 | ||
|
|
4055173bdb | ||
|
|
3f8841a276 | ||
|
|
15c38851bc | ||
|
|
05030ae817 | ||
|
|
255fa11371 | ||
|
|
a074c0bf74 | ||
|
|
6ccd649fc4 | ||
|
|
21dbea07dd | ||
|
|
4c21d9d127 | ||
|
|
766002b079 | ||
|
|
2ce430ec41 | ||
|
|
cf2b151045 | ||
|
|
92216179df | ||
|
|
a2a95b8591 | ||
|
|
4a22dc111a | ||
|
|
5df7ecc245 | ||
|
|
d4ef520f26 | ||
|
|
e0f8737865 | ||
|
|
37b13a54a7 | ||
|
|
d9d28d31d0 | ||
|
|
66bf5da3f5 | ||
|
|
840c721afc | ||
|
|
859e705a48 | ||
|
|
7375070b21 | ||
|
|
3e2eda6a71 | ||
|
|
798d327dcb | ||
|
|
f5ca9139e6 | ||
|
|
f35e8cfa51 | ||
|
|
0f6953711b | ||
|
|
e2c5e6f9ac | ||
|
|
5ef5d6ddba | ||
|
|
5b19a5d798 | ||
|
|
bf804e8923 | ||
|
|
f47b8b5528 | ||
|
|
c353f6787a | ||
|
|
1b7d5c1a5c | ||
|
|
956456f157 | ||
|
|
49077bd798 | ||
|
|
019a0287b2 | ||
|
|
a71dac28c0 | ||
|
|
155886e000 | ||
|
|
ed4c303d2e | ||
|
|
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 |
No files matched your search
@@ -60,4 +60,6 @@ body:
|
||||
- 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,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Support
|
||||
url: https://discord.gg/YPqXt63YQj
|
||||
about: Fresh new community discord providing support with all plugins, including TAB
|
||||
@@ -3,22 +3,19 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v4
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0 #https://github.com/actions/checkout/releases
|
||||
- uses: actions/checkout@v4 #https://github.com/actions/checkout/releases
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4 #https://github.com/actions/setup-java/releases
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
java-version: 21
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
@@ -41,5 +38,5 @@ jobs:
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
|
||||
with:
|
||||
name: Jar
|
||||
name: Click here to download
|
||||
path: jar/build/libs/TAB-**.jar
|
||||
+1
-2
@@ -6,5 +6,4 @@ is in quality of the report (typically providing correct steps to reproduce that
|
||||
* 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 of usage of functions.
|
||||
* [Buy a copy](https://builtbybit.com/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
|
||||
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples.
|
||||
@@ -1,70 +1,58 @@
|
||||
# About
|
||||
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
|
||||
similar plugins in terms of features, performance, compatibility and support.
|
||||
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.
|
||||
|
||||
# Distribution
|
||||
As of TAB 3.0.0, released with MC 1.18 (November 30th, 2021), there is no free / premium
|
||||
version anymore. Only one version of the plugin exists now, which contains all implemented features
|
||||
and is available to everyone for free. However, customer support is only provided to
|
||||
customers who have purchased the plugin (see [Customer support](#customer-support)).
|
||||
# Download
|
||||
### Releases
|
||||
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
|
||||
[SpigotMC](https://www.spigotmc.org/resources/57806/), [Modrinth](https://modrinth.com/plugin/tab-was-taken) or [BuiltByBit](https://builtbybit.com/resources/20631).
|
||||
|
||||
### Dev builds
|
||||
Plugin's development is continuous and contains lots of small changes/fixes not worthy of being called a full
|
||||
update, therefore development builds are being created and eventually a full update is released when enough changes are made.
|
||||
If you want to benefit from a recent change in the code before official release, you can download development builds
|
||||
from [GitHub actions](https://github.com/NEZNAMY/TAB/actions) (you must be logged into GitHub to be able to download it).
|
||||
|
||||
# Compiling
|
||||
Compilation requires JDK 17 and up.
|
||||
Compilation requires JDK 21 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.
|
||||
|
||||
# Customer support
|
||||
Unlike with other projects where requirements and level of support provided are unclear,
|
||||
we provide a clear answer to what kind of service you will or will not receive. Some projects don't
|
||||
even clearly state if they are free or paid. While they are listed as paid on spigot, they have
|
||||
a free download link in description, which can be confusing.
|
||||
Support is also quite often "we have a discord server, but don't ping devs for support", suggesting
|
||||
there is no official support, just the community.
|
||||
|
||||
In TAB, everything is clearly defined. **Downloads are free, but services are paid.**
|
||||
The plugin is free to download for everyone. The only exception might be occasional update delays
|
||||
for free users once a year.
|
||||
Customer support, however, is only provided to customers who have formally purchased a copy
|
||||
of the plugin on any of the websites the plugin is being sold on:
|
||||
* [BuiltByBit](https://builtbybit.com/resources/14009/)
|
||||
* [Polymart](https://polymart.org/resource/484)
|
||||
|
||||
After purchasing, you can request assistance via either private messages on the website where you
|
||||
have purchased it, or using [TAB's Discord server](https://discord.gg/EaSvdk6) (which is highly
|
||||
preferred, since the communication will be much faster thanks to notifications and more).
|
||||
* We use a ticket system, which allows us to effectively track any amount of unresolved problems.
|
||||
No more questions getting lost in the sea of messages from other people asking. Tickets are only closed
|
||||
after user confirmed all of their questions were answered and then receive a transcript of the ticket
|
||||
from the bot after ticket is closed into private messages (unless user is blocking them).
|
||||
* We do not rely on the community helping, which often makes things even worse. We know people have
|
||||
better things to do than helping random people with a random plugin, especially when most people only
|
||||
have a very basic knowledge of the plugin, not being able to answer more complicated problems.
|
||||
* We have notifications enabled and reply as soon as possible. We do not have the server muted unlike
|
||||
most mods on most servers who only check it when they are really bored, give a wiki link and
|
||||
disappear again.
|
||||
* As the developer, I do not rely on helpers providing support instead of me. They are handy as
|
||||
I am obviously not available 24/7, but I do not intentionally leave anything to them. I reply
|
||||
whenever I am available without any intentional delays.
|
||||
|
||||
# Other links
|
||||
Download the plugin for free:
|
||||
* [GitHub releases](https://github.com/NEZNAMY/TAB/releases)
|
||||
* [BuiltByBit](https://builtbybit.com/resources/20631/)
|
||||
* [Modrinth](https://modrinth.com/plugin/tab-was-taken)
|
||||
|
||||
[Wiki](https://github.com/NEZNAMY/TAB/wiki)
|
||||
# Documentation
|
||||
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
|
||||
of all features, as well as information regarding compatibility or limitations of each feature.
|
||||
|
||||
# Issues
|
||||
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
|
||||
In other words, they **are not available for**:
|
||||
* **Support** - support is provided on discord for buyers, where roles are given to verified users
|
||||
allowing us to know who bought it and who didn't. It also serves as a much faster way of communication.
|
||||
* **Feature requests** - closed indefinitely.
|
||||
|
||||
**If you're a customer, please use discord for reporting bugs, especially if you aren't good at making bug reports.
|
||||
You will be provided with a much faster and higher quality service.**
|
||||
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)
|
||||
|
||||
# Provided services
|
||||
The plugin is being used by a large amount of servers, which also means the demand for my attention is high. Unfortunately,
|
||||
I am no longer able to keep up with the demand since I don't want to dedicate my whole existence to a free plugin.
|
||||
In order to provide the best quality of all provided services, the amount of services provided may vary over time.
|
||||
Here is the full list of potential services and their status:
|
||||
|
||||
| Service type | Availability |
|
||||
|-------------------------------|--------------|
|
||||
| Updates for new MC versions | ✔ |
|
||||
| Bug fixes | ✔ |
|
||||
| Wiki updates<sup>1</sup> | ✔ |
|
||||
| Feature requests | ❌ |
|
||||
| Customer support<sup>2</sup> | ✔ |
|
||||
| Free user support<sup>3</sup> | ❌* |
|
||||
| Optimization<sup>4</sup> | ✔ |
|
||||
|
||||
<sup>1</sup> - Adding new examples, guides and other kind of useful information people ask about.
|
||||
<sup>2</sup> - Unrestricted assistance with the plugin for those, who bought a copy of the plugin when it was purchasable.
|
||||
<sup>3</sup> - Unlimited access to my free time for everyone in the world.
|
||||
<sup>4</sup> - Improving plugin's performance while not losing out on any features. This was done
|
||||
proactively a lot in the past, so there isn't much to do anymore, only if someone actually finds and reports something.
|
||||
|
||||
*Instead, you can use [this fresh new community discord](https://discord.gg/YPqXt63YQj) made by a nice person who will provide this service.
|
||||
@@ -1,5 +1,5 @@
|
||||
dependencies {
|
||||
compileOnlyApi("org.jetbrains:annotations:24.0.1")
|
||||
compileOnlyApi("org.jetbrains:annotations:24.1.0")
|
||||
}
|
||||
|
||||
tasks.javadoc {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -8,6 +9,7 @@ import java.util.UUID;
|
||||
/**
|
||||
* An interface representing a player
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface TabPlayer {
|
||||
|
||||
/**
|
||||
@@ -27,7 +29,7 @@ public interface TabPlayer {
|
||||
/**
|
||||
* Returns platform-specific entity
|
||||
*
|
||||
* @return an instance of bukkit/bungee player depending on platform
|
||||
* @return platform's player object
|
||||
*/
|
||||
@NotNull Object getPlayer();
|
||||
|
||||
@@ -66,4 +68,27 @@ public interface TabPlayer {
|
||||
* @see #setTemporaryGroup(String)
|
||||
*/
|
||||
boolean hasTemporaryGroup();
|
||||
|
||||
/**
|
||||
* Changes expected profile name of the player. This adapts all name-bound features
|
||||
* to use this new profile name if another plugin changed profile name of the player. <p>
|
||||
* Automatic profile name change detection is available on Bukkit, BungeeCord and Fabric,
|
||||
* therefore using this is redundant there. This function is only needed on Sponge and Velocity,
|
||||
* where the detection is not available. <p>
|
||||
* Warning: This function does NOT change player's profile name, it only updates the tracked
|
||||
* name inside the plugin.
|
||||
*
|
||||
* @param profileName
|
||||
* New expected profile name
|
||||
*/
|
||||
void setExpectedProfileName(@NonNull String profileName);
|
||||
|
||||
/**
|
||||
* Returns player's expected profile name. This defaults to player's username, but may
|
||||
* get changed by other plugins and then detected by TAB.
|
||||
*
|
||||
* @return Player's expected profile name
|
||||
*/
|
||||
@NotNull
|
||||
String getExpectedProfileName();
|
||||
}
|
||||
@@ -4,6 +4,27 @@ package me.neznamy.tab.api.bossbar;
|
||||
* An enum representing all available BossBar colors.
|
||||
* Calling ordinal() will return color's network ID.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public enum BarColor {
|
||||
PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE
|
||||
|
||||
/** Pink color */
|
||||
PINK,
|
||||
|
||||
/** Blue color */
|
||||
BLUE,
|
||||
|
||||
/** Red color */
|
||||
RED,
|
||||
|
||||
/** Green color */
|
||||
GREEN,
|
||||
|
||||
/** Yellow color */
|
||||
YELLOW,
|
||||
|
||||
/** Purple color */
|
||||
PURPLE,
|
||||
|
||||
/** White color */
|
||||
WHITE
|
||||
}
|
||||
@@ -1,22 +1,24 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* An enum representing all available BossBar styles.
|
||||
* Calling ordinal() will return style's network ID.
|
||||
* Style names are equal to NMS names.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@SuppressWarnings("unused") // API class
|
||||
public enum BarStyle {
|
||||
|
||||
PROGRESS("SOLID"),
|
||||
NOTCHED_6("SEGMENTED_6"),
|
||||
NOTCHED_10("SEGMENTED_10"),
|
||||
NOTCHED_12("SEGMENTED_12"),
|
||||
NOTCHED_20("SEGMENTED_20");
|
||||
/** Fully solid line */
|
||||
PROGRESS,
|
||||
|
||||
/** Style's name used in Bukkit API */
|
||||
@Getter private final String bukkitName;
|
||||
/** Line segmented into 6 parts */
|
||||
NOTCHED_6,
|
||||
|
||||
/** Line segmented into 10 parts */
|
||||
NOTCHED_10,
|
||||
|
||||
/** Line segmented into 12 parts */
|
||||
NOTCHED_12,
|
||||
|
||||
/** Line segmented into 20 parts */
|
||||
NOTCHED_20
|
||||
}
|
||||
@@ -193,6 +193,20 @@ public interface 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.
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* This requires the BossBar feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface BossBarManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ import lombok.NonNull;
|
||||
* The event bus. This is used to register event listeners/handlers for
|
||||
* handling events that may be fired by TAB.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface EventBus {
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* This requires the Team feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface NameTagManager {
|
||||
|
||||
/**
|
||||
@@ -150,13 +151,51 @@ public interface NameTagManager {
|
||||
*/
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
|
||||
/**
|
||||
* Returns custom prefix assigned using {@link #setPrefix(TabPlayer, String)}.
|
||||
* If no custom prefix is set, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom prefix of
|
||||
* @return Custom prefix assigned using the API.
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
@Nullable
|
||||
String getCustomPrefix(@NonNull TabPlayer player);
|
||||
|
||||
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
|
||||
/**
|
||||
* Returns custom suffix assigned using {@link #setSuffix(TabPlayer, String)}.
|
||||
* If no custom suffix is set, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom suffix of
|
||||
* @return Custom suffix assigned using the API.
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
@Nullable
|
||||
String getCustomSuffix(@NonNull TabPlayer player);
|
||||
|
||||
@NonNull String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
/**
|
||||
* Returns original prefix assigned by the plugin using internal logic.
|
||||
* @param player
|
||||
* Player to get original prefix of
|
||||
* @return Original prefix assigned by the plugin's internal logic.
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
*/
|
||||
@NonNull
|
||||
String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
/**
|
||||
* Returns original suffix assigned by the plugin using internal logic.
|
||||
* @param player
|
||||
* Player to get original suffix of
|
||||
* @return Original suffix assigned by the plugin's internal logic.
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
*/
|
||||
@NonNull
|
||||
String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Toggles name tag visibility view on all players for specified player.
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* 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.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface UnlimitedNameTagManager extends NameTagManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.function.Supplier;
|
||||
* An interface allowing placeholder registration. Instance can be
|
||||
* obtained using {@link TabAPI#getPlaceholderManager()}
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface PlaceholderManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@ package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for player placeholders (placeholders returning different value for each player)
|
||||
@@ -18,7 +19,7 @@ public interface PlayerPlaceholder extends Placeholder {
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull TabPlayer player, @NonNull Object value);
|
||||
void updateValue(@NonNull TabPlayer player, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
|
||||
@@ -2,10 +2,12 @@ package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface for relational placeholders (returning different value for each player duo)
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface RelationalPlaceholder extends Placeholder {
|
||||
|
||||
/**
|
||||
@@ -20,7 +22,7 @@ public interface RelationalPlaceholder extends Placeholder {
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @NonNull Object value);
|
||||
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface for server placeholders (returning same value for all players)
|
||||
@@ -15,7 +15,7 @@ public interface ServerPlaceholder extends Placeholder {
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull Object value);
|
||||
void updateValue(@Nullable Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.List;
|
||||
* {@link ScoreboardManager#createScoreboard(String, String, List)},
|
||||
* for scoreboards from config {@link ScoreboardManager#getRegisteredScoreboards()}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface Scoreboard {
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ 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,
|
||||
@@ -15,6 +16,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
* This requires the scoreboard feature to be enabled in config,
|
||||
* the method will return {@code null} otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface ScoreboardManager {
|
||||
|
||||
/**
|
||||
@@ -164,5 +166,5 @@ public interface ScoreboardManager {
|
||||
* player to get active scoreboard of
|
||||
* @return player's active scoreboard or {@code null} if player has no scoreboard
|
||||
*/
|
||||
@NotNull Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
|
||||
@Nullable Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* This requires the Header/footer feature to be enabled in config, otherwise
|
||||
* the method will return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface HeaderFooterManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Interface for managing player sorting by directly configuring
|
||||
* team names of players. Instance can be obtained using
|
||||
* {@link TabAPI#getSortingManager()}. If the feature is disabled,
|
||||
* it will return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface SortingManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* This requires the feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface TabListFormatManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
/**
|
||||
* Interface representing a layout.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface Layout {
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* {@link TabAPI#getLayoutManager()}. If the feature is disabled in
|
||||
* config, it will return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface LayoutManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,6 @@ plugins {
|
||||
|
||||
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")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.6")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.6-SNAPSHOT")
|
||||
}
|
||||
@@ -31,6 +31,6 @@ tasks {
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(17))
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "4.0.2"
|
||||
version = "4.1.4"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -15,11 +15,11 @@ allprojects {
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bungeecord,
|
||||
projects.krypton,
|
||||
projects.velocity,
|
||||
projects.sponge7,
|
||||
projects.sponge8,
|
||||
projects.fabric
|
||||
projects.fabric,
|
||||
projects.fabric.v1144
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
|
||||
+11
-1
@@ -4,5 +4,15 @@ dependencies {
|
||||
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")
|
||||
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
|
||||
exclude("org.bukkit", "bukkit")
|
||||
}
|
||||
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
|
||||
exclude("org.spigotmc", "spigot")
|
||||
exclude("org.spigotmc", "spigot-api")
|
||||
exclude("com.github.dmulloy2", "ProtocolLib")
|
||||
exclude("org.ow2.asm", "asm")
|
||||
exclude("net.md-5", "bungeecord-chat")
|
||||
}
|
||||
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
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;
|
||||
@@ -10,34 +12,60 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The core for bukkit forwarding events into all enabled features
|
||||
*/
|
||||
public class BukkitEventListener extends EventListener<Player> implements Listener {
|
||||
public class BukkitEventListener implements EventListener<Player>, Listener {
|
||||
|
||||
/**
|
||||
* Listens to player quit event.
|
||||
*
|
||||
* @param e
|
||||
* Disconnect event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e) {
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
/**
|
||||
* Listens to player join event.
|
||||
*
|
||||
* @param e
|
||||
* Join event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
join(e.getPlayer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to world change event.
|
||||
*
|
||||
* @param e
|
||||
* World change event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
worldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to command event to potentially cancel it.
|
||||
*
|
||||
* @param e
|
||||
* Command event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onCommand(PlayerCommandPreprocessEvent e) {
|
||||
if (command(e.getPlayer().getUniqueId(), e.getMessage())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabPlayer createPlayer(Player player) {
|
||||
return new BukkitTabPlayer(player);
|
||||
@NotNull
|
||||
public TabPlayer createPlayer(@NotNull Player player) {
|
||||
return new BukkitTabPlayer((BukkitPlatform) TAB.getInstance().getPlatform(), player);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,26 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.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.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Pipeline injection for bukkit
|
||||
* Pipeline injection for Bukkit 1.8+.
|
||||
*/
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/** Function for getting player's channel */
|
||||
private static FunctionWithException<TabPlayer, Channel> getChannel;
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
@@ -30,124 +28,48 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
/**
|
||||
* Attempts to load required classes, fields and methods and marks class as available.
|
||||
* If something fails, error message is printed and class is not marked as available.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
|
||||
} catch (IllegalAccessException exception) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
|
||||
PacketScoreboard.DisplayObjective_POSITION.getInt(packet),
|
||||
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
PacketScoreboard.Objective_METHOD.getInt(packet),
|
||||
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayObjective(@NotNull Object packet) {
|
||||
return PacketScoreboard.DisplayObjectiveClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObjective(@NotNull Object packet) {
|
||||
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeam(@NotNull Object packet) {
|
||||
return PacketScoreboard.TeamPacketClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(@NotNull Object packet) {
|
||||
return BukkitTabList.PacketPlayOutPlayerInfoClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
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_getProfile.invoke(nmsData);
|
||||
Object displayName = null;
|
||||
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, profile.getId());
|
||||
if (newDisplayName != null) displayName = nms.toNMSComponent(newDisplayName, receiver.getVersion());
|
||||
if (!nms.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
|
||||
}
|
||||
if (nms.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(
|
||||
profile.getId(),
|
||||
profile,
|
||||
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
|
||||
BukkitTabList.PlayerInfoData_Latency.getInt(nmsData),
|
||||
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
|
||||
displayName,
|
||||
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
|
||||
}
|
||||
}
|
||||
if (nms.is1_19_3Plus()) {
|
||||
BukkitTabList.PLAYERS.set(packet, updatedList);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
|
||||
if (TAB.getInstance().getNameTagManager() == null) return;
|
||||
int action = PacketScoreboard.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
|
||||
if (action == 1 || action == 2 || action == 4) return;
|
||||
Collection<String> players = (Collection<String>) PacketScoreboard.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
|
||||
String teamName = (String) PacketScoreboard.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
|
||||
if (players == null) return;
|
||||
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
|
||||
Collection<String> newList = new ArrayList<>();
|
||||
for (String entry : players) {
|
||||
TabPlayer p = TAB.getInstance().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);
|
||||
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
|
||||
"server.network.PlayerConnection", "PlayerConnection");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Field NETWORK_MANAGER;
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
|
||||
} else {
|
||||
newList.add(entry);
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
|
||||
}
|
||||
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
getChannel = player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(getHandle.invoke(player.getPlayer()))));
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "network channel injection", null,
|
||||
"Unlimited nametag mode not working and being replaced with regular nametags",
|
||||
"Anti-override for tablist & nametags not working",
|
||||
"Compatibility with nickname plugins changing player names will not work",
|
||||
"Scoreboard will not be checking for other plugins");
|
||||
}
|
||||
PacketScoreboard.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if pipeline injection is available, {@code false} if not.
|
||||
*
|
||||
* @return {@code true} if pipeline injection is available, {@code false} if not
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
return getChannel != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return getChannel.apply(player);
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Bukkit registry to register bukkit-only and universal placeholders
|
||||
*/
|
||||
public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
|
||||
private Chat chat;
|
||||
|
||||
/** NMS server to get TPS from on spigot */
|
||||
private Object server;
|
||||
|
||||
/** TPS field*/
|
||||
private Field recentTps;
|
||||
|
||||
/** Detection for presence of Paper's TPS getter */
|
||||
private Method paperTps;
|
||||
|
||||
/** Detection for presence of Paper's MSPT getter */
|
||||
private Method paperMspt;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads hooks
|
||||
*/
|
||||
public BukkitPlaceholderRegistry() {
|
||||
try {
|
||||
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
recentTps = server.getClass().getField("recentTps");
|
||||
} catch (ReflectiveOperationException e) {
|
||||
//not spigot
|
||||
}
|
||||
try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {}
|
||||
try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
|
||||
if (rspChat != null) chat = rspChat.getProvider();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
|
||||
if (paperTps != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> formatTPS(Bukkit.getTPS()[0]));
|
||||
} else if (recentTps != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> {
|
||||
try {
|
||||
return formatTPS(((double[]) recentTps.get(server))[0]);
|
||||
} catch (IllegalAccessException e) {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, -1, () -> -1);
|
||||
}
|
||||
if (paperMspt != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> format(Bukkit.getAverageTickTime()));
|
||||
}
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
|
||||
if (chat != null) {
|
||||
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
|
||||
} else {
|
||||
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
|
||||
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
|
||||
}
|
||||
super.registerPlaceholders(manager);
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.shared.GroupManager;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
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.storage.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.util.ReflectionUtils;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
import org.bukkit.Bukkit;
|
||||
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.util.Collection;
|
||||
|
||||
/**
|
||||
* Implementation of Platform interface for Bukkit platform
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
/** Plugin instance for registering tasks and events */
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
/** Variables checking presence of other plugins to hook into */
|
||||
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
|
||||
@Setter private boolean libsDisguisesEnabled = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
|
||||
|
||||
public @NotNull BossBarManagerImpl getLegacyBossBar() {
|
||||
return new WitherBossBar(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (Player p : getOnlinePlayers()) {
|
||||
TAB.getInstance().addPlayer(new BukkitTabPlayer(p));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerPlaceholders() {
|
||||
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PipelineInjector createPipelineInjector() {
|
||||
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull NameTag getUnlimitedNameTags() {
|
||||
return new BukkitNameTagX(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TabExpansion createTabExpansion() {
|
||||
if (placeholderAPI) {
|
||||
BukkitTabExpansion expansion = new BukkitTabExpansion();
|
||||
expansion.register();
|
||||
return expansion;
|
||||
}
|
||||
return new EmptyTabExpansion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TabFeature getPerWorldPlayerList() {
|
||||
return new PerWorldPlayerList(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API
|
||||
*
|
||||
* @return online players from Bukkit API
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
private @NotNull 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]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
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 ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
|
||||
} else if (identifier.startsWith("%sync:")) {
|
||||
String syncedPlaceholder = "%" + identifier.substring(6, identifier.length()-1) + "%";
|
||||
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
|
||||
ppl[0] = pl.registerPlayerPlaceholder(identifier, refresh, p -> {
|
||||
try {
|
||||
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;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
return "<Folia does not support sync placeholders>";
|
||||
}
|
||||
});
|
||||
} else if (identifier.startsWith("%server_")) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh, () ->
|
||||
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
|
||||
} else {
|
||||
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
|
||||
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
|
||||
TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerVersionInfo() {
|
||||
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0] + " (" + NMSStorage.getInstance().getServerPackage() + ")";
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -1,98 +1,24 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.*;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Main class for Bukkit platform
|
||||
* Main class for Bukkit.
|
||||
*/
|
||||
public class BukkitTAB extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
String version = Bukkit.getBukkitVersion().split("-")[0];
|
||||
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Server version: " + version + " (" + serverPackage + ")"));
|
||||
if (!isVersionSupported()) {
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
BukkitPlatform platform = new BukkitPlatform(this);
|
||||
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(version), getDataFolder()));
|
||||
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
|
||||
}
|
||||
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), this);
|
||||
TAB.getInstance().load();
|
||||
Metrics metrics = new Metrics(this, 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.PLACEHOLDER_API, () -> platform.isPlaceholderAPI() ? "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"));
|
||||
PluginCommand cmd = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
|
||||
if (cmd == null) return;
|
||||
BukkitTabCommand command = new BukkitTabCommand();
|
||||
cmd.setExecutor(command);
|
||||
cmd.setTabCompleter(command);
|
||||
boolean folia = ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer");
|
||||
TAB.create(folia ? 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 {
|
||||
long time = System.currentTimeMillis();
|
||||
NMSStorage.setInstance(getNMSLoader());
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
|
||||
return true;
|
||||
} catch (IllegalStateException ex) {
|
||||
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is not compatible. " +
|
||||
"This plugin version was made for " + ProtocolVersion.values()[ProtocolVersion.values().length-1].getFriendlyName() +
|
||||
" - " + ProtocolVersion.values()[3].getFriendlyName() + ". Disabling."));
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] A compatibility issue " +
|
||||
"with your server was found. Unless you are running some really weird server software, this is a bug."));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull NMSStorage getNMSLoader() {
|
||||
List<Callable<NMSStorage>> loaders = Arrays.asList(
|
||||
BukkitLegacyNMSStorage::new,
|
||||
BukkitModernNMSStorage::new,
|
||||
MojangModernNMSStorage::new,
|
||||
ThermosNMSStorage::new
|
||||
);
|
||||
for (Callable<NMSStorage> loader : loaders) {
|
||||
try {
|
||||
return loader.call();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
throw new IllegalStateException("Unsupported server version");
|
||||
TAB.getInstance().unload();
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -20,7 +20,7 @@ import java.util.List;
|
||||
public class BukkitTabCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
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));
|
||||
@@ -37,11 +37,12 @@ public class BukkitTabCommand implements CommandExecutor, TabCompleter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
|
||||
@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 new ArrayList<>(); //player not loaded correctly
|
||||
if (p == null) return Collections.emptyList(); //player not loaded correctly
|
||||
}
|
||||
return TAB.getInstance().getCommand().complete(p, args);
|
||||
}
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
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.lang.reflect.Method;
|
||||
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<?> PacketPlayOutPlayerListHeaderFooterClass;
|
||||
public static Constructor<?> newPacketPlayOutPlayerListHeaderFooter;
|
||||
public static Field HEADER;
|
||||
public static Field FOOTER;
|
||||
|
||||
public static Class<?> PacketPlayOutPlayerInfoClass;
|
||||
public static Constructor<?> newPacketPlayOutPlayerInfo;
|
||||
public static Field ACTION;
|
||||
public static Field PLAYERS;
|
||||
public static Class<Enum> EnumPlayerInfoActionClass;
|
||||
public static Class<Enum> EnumGamemodeClass;
|
||||
public static Class<?> ClientboundPlayerInfoRemovePacket;
|
||||
public static Class<?> RemoteChatSession$Data;
|
||||
public static Constructor<?> newClientboundPlayerInfoRemovePacket;
|
||||
|
||||
public static Class<?> PlayerInfoDataClass;
|
||||
public static Constructor<?> newPlayerInfoData;
|
||||
public static Method PlayerInfoData_getProfile;
|
||||
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;
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
public static void load(NMSStorage nms) throws NoSuchMethodException {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor(nms.IChatBaseComponent, nms.IChatBaseComponent);
|
||||
} else {
|
||||
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor();
|
||||
HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(0);
|
||||
FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(1);
|
||||
}
|
||||
if (nms.is1_19_3Plus()) {
|
||||
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
|
||||
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
|
||||
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumSet.class);
|
||||
} else {
|
||||
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumPlayerInfoActionClass, Array.newInstance(nms.EntityPlayer, 0).getClass());
|
||||
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumPlayerInfoActionClass);
|
||||
}
|
||||
PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, List.class);
|
||||
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoDataClass);
|
||||
PlayerInfoData_getProfile = ReflectionUtils.getOnlyMethod(PlayerInfoDataClass, GameProfile.class);
|
||||
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(PlayerInfoDataClass, int.class);
|
||||
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoDataClass, EnumGamemodeClass);
|
||||
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoDataClass, nms.IChatBaseComponent);
|
||||
if (nms.is1_19_3Plus()) {
|
||||
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(PlayerInfoDataClass, boolean.class);
|
||||
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(PlayerInfoDataClass, RemoteChatSession$Data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
if (ClientboundPlayerInfoRemovePacket != null) {
|
||||
//1.19.3+
|
||||
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
|
||||
} else {
|
||||
//1.19.2-
|
||||
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Entry.Builder(entry).build(), player.getVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME,
|
||||
new Entry.Builder(entry).displayName(displayName).build(), player.getVersion())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
player.sendPacket(createPacket(Action.UPDATE_LATENCY,
|
||||
new Entry.Builder(entry).latency(latency).build(), player.getVersion())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE,
|
||||
new Entry.Builder(entry).gameMode(gameMode).build(), player.getVersion())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
player.sendPacket(createPacket(Action.ADD_PLAYER, entry, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
if (PacketPlayOutPlayerListHeaderFooterClass == null) return;
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object packet;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance(
|
||||
nms.toNMSComponent(header, player.getVersion()), nms.toNMSComponent(footer, player.getVersion()));
|
||||
} else {
|
||||
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance();
|
||||
HEADER.set(packet, nms.toNMSComponent(header, player.getVersion()));
|
||||
FOOTER.set(packet, nms.toNMSComponent(footer, player.getVersion()));
|
||||
}
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createPacket(TabList.Action action, TabList.Entry entry, ProtocolVersion clientVersion) {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
Object packet;
|
||||
List<Object> players = new ArrayList<>();
|
||||
if (NMSStorage.getInstance().is1_19_3Plus()) {
|
||||
EnumSet<?> actions;
|
||||
if (action == TabList.Action.ADD_PLAYER) {
|
||||
actions = EnumSet.allOf(EnumPlayerInfoActionClass);
|
||||
} else {
|
||||
actions = EnumSet.of(Enum.valueOf(EnumPlayerInfoActionClass, action.name()));
|
||||
}
|
||||
packet = newPacketPlayOutPlayerInfo.newInstance(actions, Collections.emptyList());
|
||||
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
|
||||
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
|
||||
players.add(newPlayerInfoData.newInstance(
|
||||
entry.getUniqueId(),
|
||||
profile,
|
||||
true,
|
||||
entry.getLatency(),
|
||||
int2GameMode(entry.getGameMode()),
|
||||
entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion),
|
||||
null
|
||||
));
|
||||
} else {
|
||||
packet = newPacketPlayOutPlayerInfo.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action.name()),
|
||||
Array.newInstance(NMSStorage.getInstance().EntityPlayer, 0));
|
||||
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
|
||||
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (newPlayerInfoData.getParameterTypes()[0] == PacketPlayOutPlayerInfoClass) {
|
||||
parameters.add(packet);
|
||||
}
|
||||
parameters.add(profile);
|
||||
parameters.add(entry.getLatency());
|
||||
parameters.add(int2GameMode(entry.getGameMode()));
|
||||
parameters.add(entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion));
|
||||
if (nms.getMinorVersion() >= 19) parameters.add(null);
|
||||
players.add(newPlayerInfoData.newInstance(parameters.toArray()));
|
||||
}
|
||||
PLAYERS.set(packet, players);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private Object int2GameMode(int gameMode) {
|
||||
switch (gameMode) {
|
||||
case 1: return Enum.valueOf(EnumGamemodeClass, "CREATIVE");
|
||||
case 2: return Enum.valueOf(EnumGamemodeClass, "ADVENTURE");
|
||||
case 3: return Enum.valueOf(EnumGamemodeClass, "SPECTATOR");
|
||||
default: return Enum.valueOf(EnumGamemodeClass, "SURVIVAL");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,26 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.shared.hook.ViaVersionHook;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BossBarLoader;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
|
||||
import me.neznamy.tab.platforms.bukkit.entity.PacketEntityView;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
|
||||
import me.neznamy.tab.shared.backend.entityview.DummyEntityView;
|
||||
import me.neznamy.tab.shared.backend.entityview.EntityView;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
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.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for Bukkit platform
|
||||
*/
|
||||
@@ -38,29 +28,28 @@ import java.util.UUID;
|
||||
@Getter
|
||||
public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
/** Player's NMS handle (EntityPlayer), preloading for speed */
|
||||
private final Object handle;
|
||||
@NotNull
|
||||
private final Scoreboard<BukkitTabPlayer, ?> scoreboard = ScoreboardLoader.getInstance().apply(this);
|
||||
|
||||
/** Player's connection for sending packets, preloading for speed */
|
||||
private final Object playerConnection;
|
||||
@NotNull
|
||||
private final TabListBase<?> tabList = TabListBase.getInstance().apply(this);
|
||||
|
||||
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
|
||||
private final TabList tabList = new BukkitTabList(this);
|
||||
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
|
||||
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
|
||||
@NotNull
|
||||
private final BossBar bossBar = BossBarLoader.findInstance(this);
|
||||
|
||||
@NotNull
|
||||
private final EntityView entityView = PacketEntityView.isAvailable() ? new PacketEntityView(this) : new DummyEntityView();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given bukkit player and protocol version
|
||||
* Constructs new instance with given bukkit player
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param p
|
||||
* bukkit player
|
||||
*/
|
||||
@SneakyThrows
|
||||
public BukkitTabPlayer(Player p) {
|
||||
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(),
|
||||
p.getWorld().getName(), ViaVersionHook.getInstance().getPlayerVersion(p.getUniqueId(), p.getName()));
|
||||
handle = NMSStorage.getInstance().getHandle.invoke(player);
|
||||
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
|
||||
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
|
||||
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName(), platform.getServerVersion().getNetworkId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,26 +59,12 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
|
||||
return getPlayer().getPing();
|
||||
}
|
||||
try {
|
||||
return NMSStorage.getInstance().PING.getInt(handle);
|
||||
} catch (IllegalAccessException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void sendPacket(@Nullable Object nmsPacket) {
|
||||
if (nmsPacket == null || !getPlayer().isOnline()) return;
|
||||
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
|
||||
return PingRetriever.getPing(getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
getPlayer().sendMessage(RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
|
||||
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendMessage(getPlatform().toBukkitFormat(message, getVersion().supportsRGB()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,39 +74,32 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
@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, getPlayer());
|
||||
} catch (LinkageError | ReflectiveOperationException e) {
|
||||
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
|
||||
TAB.getInstance().getErrorManager().printError("Failed to check disguise status using LibsDisguises", e);
|
||||
((BukkitPlatform)TAB.getInstance().getPlatform()).setLibsDisguisesEnabled(false);
|
||||
return false;
|
||||
}
|
||||
return LibsDisguisesHook.isDisguised(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@Nullable
|
||||
public TabList.Skin getSkin() {
|
||||
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
return new TabList.Skin(property.getValue(), property.getSignature());
|
||||
return tabList.getSkin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Player getPlayer() {
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
return (Player) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer().isOnline();
|
||||
public BukkitPlatform getPlatform() {
|
||||
return (BukkitPlatform) platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
|
||||
for (MetadataValue v : getPlayer().getMetadata("vanished")) {
|
||||
if (v.asBoolean()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,39 +108,13 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data) {
|
||||
sendPacket(PacketPlayOutSpawnEntityLivingStorage.build(entityId, id, entityType, location, data));
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
|
||||
updateEntityMetadata(entityId, data);
|
||||
}
|
||||
public double getHealth() {
|
||||
return getPlayer().getHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
if (PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.getParameterCount() == 2) {
|
||||
//1.19.3+
|
||||
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(data.build())));
|
||||
} else {
|
||||
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, data.build(), true));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleportEntity(int entityId, @NotNull Location location) {
|
||||
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void destroyEntities(int... entities) {
|
||||
if (PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.getParameterTypes()[0] != int.class) {
|
||||
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(new Object[]{entities}));
|
||||
} else {
|
||||
//1.17.0 Mojank
|
||||
for (int entity : entities) {
|
||||
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(entity));
|
||||
}
|
||||
}
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return getPlayer().getDisplayName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Class containing static util methods used on Bukkit.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class BukkitUtils {
|
||||
|
||||
/** Whether compatibility exceptions should be printed or not, enabling when adding support for new versions */
|
||||
public static final boolean PRINT_EXCEPTIONS = false;
|
||||
|
||||
private static boolean compatibilityIssue;
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a console warn that some compatibility issue was found.
|
||||
*
|
||||
* @param exception
|
||||
* Exception thrown when initializing fields, if applicable
|
||||
* @param failedCheck
|
||||
* Check that failed
|
||||
* @param fallback
|
||||
* Fallback implementation, if available
|
||||
* @param missingFeatures
|
||||
* Features that will be broken because of the incompatibility
|
||||
*/
|
||||
public static void compatibilityError(@NotNull Exception exception, @NotNull String failedCheck,
|
||||
@Nullable String fallback, @NotNull String... missingFeatures) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(EnumChatFormat.RED);
|
||||
sb.append("[TAB] Failed to initialize minecraft fields for ");
|
||||
sb.append(failedCheck);
|
||||
sb.append(" due to a compatibility error. ");
|
||||
if (fallback != null) {
|
||||
sb.append("Using fallback solution using ").append(fallback).append(". ");
|
||||
} else {
|
||||
sb.append("No fallback solution was found. ");
|
||||
}
|
||||
if (missingFeatures.length > 0) sb.append("This will result in: ");
|
||||
for (int i=0; i<missingFeatures.length; i++) {
|
||||
sb.append("\n").append("#").append(i + 1).append(": ").append(missingFeatures[i]);
|
||||
}
|
||||
Bukkit.getConsoleSender().sendMessage(sb.toString());
|
||||
if (PRINT_EXCEPTIONS) exception.printStackTrace();
|
||||
compatibilityIssue = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message asking user to update the plugin if some compatibility issue was found.
|
||||
*/
|
||||
public static void sendCompatibilityMessage() {
|
||||
if (!compatibilityIssue) return;
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] Please update the plugin to " +
|
||||
"a version with native support for your server version for optimal experience. This plugin version " +
|
||||
"was made for " + ProtocolVersion.V1_5.getFriendlyName() + " - " + ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName() + ".");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Class for finding BossBar implementation based on server and client version.
|
||||
*/
|
||||
public class BossBarLoader {
|
||||
|
||||
/**
|
||||
* Finds best available instance for given player.
|
||||
*
|
||||
* @param player
|
||||
* Player to find instance for
|
||||
* @return BossBar instance for player
|
||||
*/
|
||||
@NotNull
|
||||
public static BossBar findInstance(@NotNull BukkitTabPlayer player) {
|
||||
// 1.9+ server, handle using API, potential 1.8 players are handled by ViaVersion
|
||||
if (BukkitReflection.getMinorVersion() >= 9) return new BukkitBossBar(player);
|
||||
|
||||
// 1.9+ player on 1.8 server, handle using ViaVersion API
|
||||
if (player.getVersion().getMinorVersion() >= 9) return new ViaBossBar(player);
|
||||
|
||||
// 1.8- server and player, use wither
|
||||
return new EntityBossBar(player);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,9 @@ 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.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -21,20 +20,25 @@ import java.util.UUID;
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitBossBar implements BossBar {
|
||||
|
||||
/** Style array for fast access */
|
||||
private static final org.bukkit.boss.BarStyle[] styles = org.bukkit.boss.BarStyle.values();
|
||||
|
||||
/** 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(
|
||||
RGBUtils.getInstance().convertToBukkitFormat(title,
|
||||
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
|
||||
player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()),
|
||||
org.bukkit.boss.BarColor.valueOf(color.name()),
|
||||
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
|
||||
styles[style.ordinal()]
|
||||
);
|
||||
bar.setProgress(progress);
|
||||
bar.addPlayer(player.getPlayer());
|
||||
bossBars.put(id, bar);
|
||||
@@ -42,8 +46,7 @@ public class BukkitBossBar implements BossBar {
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
bossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
|
||||
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
|
||||
bossBars.get(id).setTitle(player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,7 +56,7 @@ public class BukkitBossBar implements BossBar {
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
|
||||
bossBars.get(id).setStyle(styles[style.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.platform.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.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.entity.DataWatcher;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -20,35 +20,45 @@ import java.util.UUID;
|
||||
@RequiredArgsConstructor
|
||||
public class EntityBossBar implements BossBar {
|
||||
|
||||
/** Max health of wither */
|
||||
private static final int WITHER_MAX_HEALTH = 300;
|
||||
|
||||
/** Ideal value for invulnerable time for minimal wither size */
|
||||
private static final int IDEAL_INVULNERABLE_TIME = 880;
|
||||
|
||||
/** Flag making entity invisible */
|
||||
private static final byte INVISIBLE_FLAG = 1 << 5;
|
||||
|
||||
/** 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;
|
||||
float health = WITHER_MAX_HEALTH*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.getHelper().setHealth(health);
|
||||
w.getHelper().setCustomName(title, player.getVersion());
|
||||
w.getHelper().setEntityFlags((byte) 32);
|
||||
w.getHelper().setWitherInvulnerableTime(880); // Magic number
|
||||
player.spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0, 0, 0), w);
|
||||
w.setHealth(health);
|
||||
w.setCustomName(title, player.getVersion());
|
||||
w.setEntityFlags(INVISIBLE_FLAG);
|
||||
w.setWitherInvulnerableTime(IDEAL_INVULNERABLE_TIME);
|
||||
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.getHelper().setCustomName(title, player.getVersion());
|
||||
player.updateEntityMetadata(id.hashCode(), w);
|
||||
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;
|
||||
float health = WITHER_MAX_HEALTH*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.getHelper().setHealth(health);
|
||||
player.updateEntityMetadata(id.hashCode(), w);
|
||||
w.setHealth(health);
|
||||
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,6 +69,6 @@ public class EntityBossBar implements BossBar {
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
player.destroyEntities(id.hashCode());
|
||||
player.getEntityView().destroyEntities(id.hashCode());
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ 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.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -21,28 +21,33 @@ import java.util.UUID;
|
||||
@RequiredArgsConstructor
|
||||
public class ViaBossBar implements BossBar {
|
||||
|
||||
/** Style array for fast access */
|
||||
private static final BossStyle[] styles = BossStyle.values();
|
||||
|
||||
/** 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(
|
||||
RGBUtils.getInstance().convertToBukkitFormat(title, player.getVersion().getMinorVersion() >= 16),
|
||||
TabComponent.optimized(title).toLegacyText(),
|
||||
progress,
|
||||
BossColor.valueOf(color.name()),
|
||||
BossStyle.valueOf(style.getBukkitName()));
|
||||
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(RGBUtils.getInstance().convertToBukkitFormat(title,
|
||||
player.getVersion().getMinorVersion() >= 16));
|
||||
viaBossBars.get(id).setTitle(TabComponent.optimized(title).toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,7 +57,7 @@ public class ViaBossBar implements BossBar {
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
viaBossBars.get(id).setStyle(BossStyle.valueOf(style.getBukkitName()));
|
||||
viaBossBars.get(id).setStyle(styles[style.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
package me.neznamy.tab.platforms.bukkit.entity;
|
||||
|
||||
import lombok.*;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
|
||||
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.backend.EntityData;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcher class to make work with it much easier
|
||||
*/
|
||||
@ToString
|
||||
public class DataWatcher implements EntityData {
|
||||
|
||||
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 = EntityData.getArmorStandFlagsPosition(BukkitReflection.getMinorVersion());
|
||||
|
||||
/** 1.19.3+ */
|
||||
private static Constructor<?> newDataWatcher$Item;
|
||||
|
||||
/** 1.19.2- */
|
||||
public static Class<?> DataWatcher;
|
||||
private static Constructor<?> newDataWatcher;
|
||||
private static Method DataWatcher_register;
|
||||
private static Constructor<?> newDataWatcherObject;
|
||||
|
||||
/** Watched data */
|
||||
private final Map<Integer, Item> dataValues = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
if (minorVersion >= 9) {
|
||||
loadSerializers();
|
||||
}
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
Class<?> dataWatcher$Item = BukkitReflection.getClass("network.syncher.SynchedEntityData$DataValue", "network.syncher.DataWatcher$c", "network.syncher.DataWatcher$b");
|
||||
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
|
||||
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
|
||||
newDataWatcher$Item = dataWatcher$Item.getConstructor(int.class, dataWatcherSerializer, Object.class);
|
||||
} else {
|
||||
DataWatcher = BukkitReflection.getClass("network.syncher.SynchedEntityData", "network.syncher.DataWatcher", "DataWatcher");
|
||||
if (minorVersion >= 7) {
|
||||
ComponentConverter.ensureAvailable();
|
||||
newDataWatcher = DataWatcher.getConstructor(BukkitReflection.getClass("world.entity.Entity", "Entity"));
|
||||
} else {
|
||||
newDataWatcher = DataWatcher.getConstructor();
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
Class<?> dataWatcherObject = BukkitReflection.getClass("network.syncher.EntityDataAccessor",
|
||||
"network.syncher.DataWatcherObject", "DataWatcherObject");
|
||||
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
|
||||
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
|
||||
DataWatcher_register = ReflectionUtils.getMethod(
|
||||
DataWatcher,
|
||||
new String[]{"define", "register", "a", "m_135372_"}, // {Mojang, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
|
||||
dataWatcherObject, Object.class
|
||||
);
|
||||
newDataWatcherObject = dataWatcherObject.getConstructor(int.class, dataWatcherSerializer);
|
||||
} else {
|
||||
DataWatcher_register = ReflectionUtils.getMethod(
|
||||
DataWatcher,
|
||||
new String[]{"func_75682_a", "a"}, int.class, // {Thermos 1.7.10, Bukkit}
|
||||
Object.class
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadSerializers() throws ReflectiveOperationException {
|
||||
Class<?> dataWatcherRegistry = BukkitReflection.getClass("network.syncher.EntityDataSerializers",
|
||||
"network.syncher.DataWatcherRegistry", "DataWatcherRegistry");
|
||||
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 (BukkitReflection.getMinorVersion() >= 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(TabComponent.optimized(customName).convert(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() > Limitations.BOSSBAR_NAME_LENGTH_1_7 ?
|
||||
customName.substring(0, Limitations.BOSSBAR_NAME_LENGTH_1_7) : 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
|
||||
@NotNull
|
||||
public Object build() {
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
List<Object> items = new ArrayList<>();
|
||||
for (Item item : dataValues.values()) {
|
||||
items.add(newDataWatcher$Item.newInstance(item.position, item.serializer, item.value));
|
||||
}
|
||||
return items;
|
||||
} else {
|
||||
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());
|
||||
}
|
||||
return nmsWatcher;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for specific data watcher item.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
private static class Item {
|
||||
|
||||
/** Item position */
|
||||
private final int position;
|
||||
|
||||
/** Item serializer (1.9+) */
|
||||
@Nullable
|
||||
private final Object serializer;
|
||||
|
||||
/** Item value */
|
||||
@NonNull
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates data watcher object with position and serializer
|
||||
*
|
||||
* @return NMS data watcher object
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object createObject() {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
return newDataWatcherObject.newInstance(position, serializer);
|
||||
} else {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
package me.neznamy.tab.platforms.bukkit.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
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.platforms.bukkit.nms.PacketSender;
|
||||
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.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* EntityView implementation for Bukkit using packets.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@SuppressWarnings("unchecked")
|
||||
public class PacketEntityView implements EntityView {
|
||||
|
||||
/** Version with entity metadata being split from spawn packet */
|
||||
private static final int SPLIT_METADATA_VERSION = 15;
|
||||
|
||||
private static PacketSender packetSender;
|
||||
|
||||
/** PacketPlayOutEntityDestroy */
|
||||
private static Class<?> EntityDestroyClass;
|
||||
private static BiConsumerWithException<BukkitTabPlayer, int[]> destroyEntities;
|
||||
private static FunctionWithException<Object, int[]> getDestroyedEntities;
|
||||
|
||||
/** PacketPlayOutEntityMetadata */
|
||||
private static BiFunctionWithException<Integer, EntityData, Object> newEntityMetadata;
|
||||
|
||||
/** PacketPlayOutEntityTeleport */
|
||||
private static BiFunctionWithException<Integer, Location, Object> newEntityTeleport;
|
||||
private static Class<?> EntityTeleportClass;
|
||||
private static Field EntityTeleport_EntityId;
|
||||
|
||||
/** PacketPlayOutSpawnEntityLiving */
|
||||
private static QuintFunction<Integer, UUID, Object, Location, EntityData, Object> newSpawnEntity;
|
||||
|
||||
/** Other entity packets */
|
||||
private static Class<?> PacketPlayOutEntity;
|
||||
private static Field PacketPlayOutEntity_ENTITYID;
|
||||
private static Field PacketPlayOutEntity_X;
|
||||
private static Field PacketPlayOutEntity_Y;
|
||||
private static Field PacketPlayOutEntity_Z;
|
||||
private static Class<?> PacketPlayOutEntityLook;
|
||||
private static QuadFunction<Integer, Long, Long, Long, Object> newMovePacket;
|
||||
private static Class<?> PacketPlayOutNamedEntitySpawn;
|
||||
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
|
||||
|
||||
private static Constructor<?> newClientboundBundlePacket;
|
||||
private static Field ClientboundBundlePacket_packets;
|
||||
private static Function<Object, Boolean> isBundlePacket = packet -> false;
|
||||
private static BiConsumerWithException<BukkitTabPlayer, Iterable<?>> sendAsBundle = (player, packets) -> {
|
||||
for (Object packet : packets) {
|
||||
packetSender.sendPacket(player.getPlayer(), packet);
|
||||
}
|
||||
};
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
/** Player this view belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
/**
|
||||
* Loads all required classes and fields and marks class as available.
|
||||
* If something goes wrong, a warning is printed and class is not marked as available.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
DataWatcher.load();
|
||||
loadEntityMetadata();
|
||||
loadEntityDestroy();
|
||||
loadEntityTeleport();
|
||||
loadEntityMove();
|
||||
loadEntitySpawn();
|
||||
if (BukkitReflection.is1_19_4Plus()) {
|
||||
Class<?> ClientboundBundlePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundBundlePacket");
|
||||
newClientboundBundlePacket = ClientboundBundlePacket.getConstructor(Iterable.class);
|
||||
ClientboundBundlePacket_packets = ReflectionUtils.getOnlyField(ClientboundBundlePacket.getSuperclass(), Iterable.class);
|
||||
isBundlePacket = ClientboundBundlePacket::isInstance;
|
||||
sendAsBundle = (player, packets) -> packetSender.sendPacket(player.getPlayer(), newClientboundBundlePacket.newInstance(packets));
|
||||
}
|
||||
packetSender = new PacketSender();
|
||||
available = true;
|
||||
} catch (Exception e) {
|
||||
List<String> missingFeatures = new ArrayList<>();
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
missingFeatures.add("Unlimited nametag mode not working and being replaced with regular nametags");
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() <= 8) {
|
||||
missingFeatures.add("BossBar feature not working");
|
||||
}
|
||||
BukkitUtils.compatibilityError(e, "sending entity packets", null, missingFeatures.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("JavaReflectionInvocation")
|
||||
private static void loadEntityMetadata() throws ReflectiveOperationException {
|
||||
Class<?> entityMetadataClass = BukkitReflection.getClass("network.protocol.game.ClientboundSetEntityDataPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
Constructor<?> constructor = entityMetadataClass.getConstructor(int.class, List.class);
|
||||
newEntityMetadata = (entityId, data) -> constructor.newInstance(entityId, data.build());
|
||||
} else {
|
||||
Constructor<?> constructor = entityMetadataClass.getConstructor(int.class, DataWatcher.DataWatcher, boolean.class);
|
||||
newEntityMetadata = (entityId, data) -> constructor.newInstance(entityId, data.build(), true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadEntityDestroy() throws ReflectiveOperationException {
|
||||
EntityDestroyClass = BukkitReflection.getClass("network.protocol.game.ClientboundRemoveEntitiesPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
|
||||
Field entities = ReflectionUtils.getOnlyField(EntityDestroyClass);
|
||||
try {
|
||||
Constructor<?> constructor = EntityDestroyClass.getConstructor(int[].class);
|
||||
destroyEntities = (player, ids) -> packetSender.sendPacket(player.getPlayer(), constructor.newInstance(new Object[]{ids}));
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
getDestroyedEntities = packet -> ((List<Integer>) entities.get(packet)).stream().mapToInt(i -> i).toArray();
|
||||
} else {
|
||||
getDestroyedEntities = packet -> (int[]) entities.get(packet);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
//1.17.0 Mojank
|
||||
Constructor<?> constructor = EntityDestroyClass.getConstructor(int.class);
|
||||
destroyEntities = (player, ids) -> { for (int entity : ids) packetSender.sendPacket(player.getPlayer(), constructor.newInstance(entity));};
|
||||
getDestroyedEntities = packet -> new int[]{entities.getInt(packet)};
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadEntityTeleport() throws ReflectiveOperationException {
|
||||
EntityTeleportClass = BukkitReflection.getClass("network.protocol.game.ClientboundTeleportEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
|
||||
Callable<Object> newPacket;
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
// Dummy armor stand for constructor
|
||||
Class<?> world = BukkitReflection.getClass("world.level.Level", "world.level.World", "World");
|
||||
Class<?> entityArmorStand = BukkitReflection.getClass("world.entity.decoration.ArmorStand",
|
||||
"world.entity.decoration.EntityArmorStand", "EntityArmorStand");
|
||||
Constructor<?> newEntityArmorStand = entityArmorStand.getConstructor(world, double.class, double.class, double.class);
|
||||
Method World_getHandle = BukkitReflection.getBukkitClass("CraftWorld").getMethod("getHandle");
|
||||
Object dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
|
||||
|
||||
Constructor<?> constructor = EntityTeleportClass.getConstructor(BukkitReflection.getClass("world.entity.Entity"));
|
||||
newPacket = () -> constructor.newInstance(dummyEntity);
|
||||
} else {
|
||||
Constructor<?> constructor = EntityTeleportClass.getConstructor();
|
||||
newPacket = constructor::newInstance;
|
||||
}
|
||||
|
||||
EntityTeleport_EntityId = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(0);
|
||||
Field EntityTeleport_X;
|
||||
Field EntityTeleport_Y;
|
||||
Field EntityTeleport_Z;
|
||||
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);
|
||||
}
|
||||
newEntityTeleport = (entityId, location) -> {
|
||||
Object packet = newPacket.call();
|
||||
EntityTeleport_EntityId.set(packet, entityId);
|
||||
EntityTeleport_X.set(packet, toPosition(location.getX()));
|
||||
EntityTeleport_Y.set(packet, toPosition(location.getY()));
|
||||
EntityTeleport_Z.set(packet, toPosition(location.getZ()));
|
||||
return packet;
|
||||
};
|
||||
}
|
||||
|
||||
private static void loadEntityMove() throws ReflectiveOperationException {
|
||||
// Classes
|
||||
PacketPlayOutEntity = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
|
||||
PacketPlayOutEntityLook = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket$Rot",
|
||||
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook",
|
||||
"PacketPlayOutEntityLook", "Packet32EntityLook");
|
||||
Class<?> packetPlayOutRelEntityMove = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket$Pos",
|
||||
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutRelEntityMove", "PacketPlayOutEntity$PacketPlayOutRelEntityMove",
|
||||
"PacketPlayOutRelEntityMove", "Packet31RelEntityMove");
|
||||
|
||||
|
||||
// Fields
|
||||
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
|
||||
|
||||
if (BukkitReflection.getMinorVersion() >= 14) {
|
||||
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, short.class);
|
||||
PacketPlayOutEntity_X = fields.get(0);
|
||||
PacketPlayOutEntity_Y = fields.get(1);
|
||||
PacketPlayOutEntity_Z = fields.get(2);
|
||||
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, short.class, short.class, short.class, boolean.class);
|
||||
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x.shortValue(), y.shortValue(), z.shortValue(), false);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, int.class);
|
||||
PacketPlayOutEntity_X = fields.get(1);
|
||||
PacketPlayOutEntity_Y = fields.get(2);
|
||||
PacketPlayOutEntity_Z = fields.get(3);
|
||||
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, long.class, long.class, long.class, boolean.class);
|
||||
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x, y, z, false);
|
||||
} else if (BukkitReflection.getMinorVersion() == 8) {
|
||||
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, byte.class);
|
||||
PacketPlayOutEntity_X = fields.get(0);
|
||||
PacketPlayOutEntity_Y = fields.get(1);
|
||||
PacketPlayOutEntity_Z = fields.get(2);
|
||||
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, byte.class, byte.class, byte.class, boolean.class);
|
||||
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x.byteValue(), y.byteValue(), z.byteValue(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads required NMS classes, fields and methods. If it fails, exception is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If anything fails
|
||||
*/
|
||||
private static void loadEntitySpawn() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
|
||||
// Entity ids
|
||||
EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
|
||||
if (minorVersion >= 13) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 1);
|
||||
} else {
|
||||
entityIds.put(EntityType.WITHER, 64);
|
||||
if (minorVersion >= 8) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 30);
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> SpawnEntityClass = BukkitReflection.getClass("network.protocol.game.ClientboundAddEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutSpawnEntity", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
|
||||
Field SpawnEntity_EntityId = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(0);
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
PacketPlayOutNamedEntitySpawn = SpawnEntityClass;
|
||||
PacketPlayOutNamedEntitySpawn_ENTITYID = SpawnEntity_EntityId;
|
||||
} else {
|
||||
PacketPlayOutNamedEntitySpawn = BukkitReflection.getClass("network.protocol.game.ClientboundAddPlayerPacket",
|
||||
"network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
|
||||
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
|
||||
}
|
||||
Constructor<?> constructor;
|
||||
|
||||
// 1.17+ constructor using Spawn entity packet
|
||||
if (minorVersion >= 17) {
|
||||
Class<?> Vec3D = BukkitReflection.getClass("world.phys.Vec3", "world.phys.Vec3D");
|
||||
Object Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
|
||||
Class<?> EntityTypes = BukkitReflection.getClass("world.entity.EntityType", "world.entity.EntityTypes");
|
||||
if (minorVersion >= 19) {
|
||||
Object EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "d").get(null);
|
||||
constructor = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
|
||||
newSpawnEntity = (id, uuid, type, l, data) -> constructor.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0.0d);
|
||||
} else {
|
||||
Object EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "c", "f_20529_").get(null); // Mohist 1.18.2
|
||||
constructor = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
|
||||
newSpawnEntity = (id, uuid, type, l, data) -> constructor.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.16- constructor using spawn entity living packet
|
||||
constructor = SpawnEntityClass.getConstructor();
|
||||
@Nullable Field SpawnEntity_UUID;
|
||||
Field SpawnEntity_X;
|
||||
Field SpawnEntity_Y;
|
||||
Field SpawnEntity_Z;
|
||||
@Nullable Field SpawnEntity_DataWatcher;
|
||||
Field SpawnEntity_EntityType = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(1);
|
||||
if (minorVersion >= 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_UUID = null;
|
||||
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);
|
||||
}
|
||||
if (minorVersion < SPLIT_METADATA_VERSION) {
|
||||
SpawnEntity_DataWatcher = ReflectionUtils.getOnlyField(SpawnEntityClass, DataWatcher.DataWatcher);
|
||||
} else {
|
||||
SpawnEntity_DataWatcher = null;
|
||||
}
|
||||
newSpawnEntity = (id, uuid, type, l, data) -> {
|
||||
Object nmsPacket = constructor.newInstance();
|
||||
SpawnEntity_EntityId.set(nmsPacket, id);
|
||||
if (minorVersion < SPLIT_METADATA_VERSION) SpawnEntity_DataWatcher.set(nmsPacket, data.build());
|
||||
if (minorVersion >= 9) SpawnEntity_UUID.set(nmsPacket, uuid);
|
||||
SpawnEntity_X.set(nmsPacket, toPosition(l.getX()));
|
||||
SpawnEntity_Y.set(nmsPacket, toPosition(l.getY()));
|
||||
SpawnEntity_Z.set(nmsPacket, toPosition(l.getZ()));
|
||||
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) type));
|
||||
return nmsPacket;
|
||||
};
|
||||
}
|
||||
|
||||
private static Object toPosition(double paramDouble) {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
return paramDouble;
|
||||
} else {
|
||||
int i = (int) (paramDouble*32);
|
||||
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) {
|
||||
if (BukkitReflection.getMinorVersion() >= SPLIT_METADATA_VERSION) {
|
||||
sendAsBundle.accept(player, Arrays.asList(
|
||||
newSpawnEntity.apply(entityId, id, entityType, l, null),
|
||||
newEntityMetadata.apply(entityId, data)
|
||||
));
|
||||
} else {
|
||||
packetSender.sendPacket(player.getPlayer(), newSpawnEntity.apply(entityId, id, entityType, l, data));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
packetSender.sendPacket(player.getPlayer(), newEntityMetadata.apply(entityId, data));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void teleportEntity(int entityId, @NotNull Location location) {
|
||||
packetSender.sendPacket(player.getPlayer(), newEntityTeleport.apply(entityId, location));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void destroyEntities(int... entities) {
|
||||
destroyEntities.accept(player, entities);
|
||||
}
|
||||
|
||||
@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) {
|
||||
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) {
|
||||
return PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(playerSpawnPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
|
||||
return getDestroyedEntities.apply(destroyPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundlePacket(@NotNull Object packet) {
|
||||
return isBundlePacket.apply(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
|
||||
return (Iterable<Object>) ClientboundBundlePacket_packets.get(bundlePacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Location getMoveDiff(@NotNull Object movePacket) {
|
||||
return new Location(
|
||||
((Number)PacketPlayOutEntity_X.get(movePacket)).intValue(),
|
||||
((Number)PacketPlayOutEntity_Y.get(movePacket)).intValue(),
|
||||
((Number)PacketPlayOutEntity_Z.get(movePacket)).intValue()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void moveEntity(int entityId, @NotNull Location moveDiff) {
|
||||
packetSender.sendPacket(player.getPlayer(), newMovePacket.apply(
|
||||
entityId, (long) moveDiff.getX(), (long) moveDiff.getY(), (long) moveDiff.getZ()));
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,13 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.entity.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.features.types.PacketSendListener;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
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;
|
||||
@@ -30,61 +26,47 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The core class for unlimited NameTag mode on Bukkit
|
||||
*/
|
||||
public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketSendListener {
|
||||
public class BukkitNameTagX extends BackendNameTagX implements Listener {
|
||||
|
||||
/** Reference to NMS storage for quick access */
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
/** Version in which multiple passenger option was added */
|
||||
private static final int MULTI_PASSENGER_VERSION = 11;
|
||||
|
||||
/**
|
||||
* Constructs new instance and registers the class listener.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance to use for registering listener
|
||||
*/
|
||||
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to player sneak event to sneak armor stands.
|
||||
*
|
||||
* @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 || isPlayerDisabled(p)) return;
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_SNEAK,
|
||||
() -> getArmorStandManager(p).sneak(e.isSneaking()));
|
||||
sneak(e.getPlayer().getUniqueId(), e.isSneaking());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
/**
|
||||
* Listener to respawn event to resend armor stands.
|
||||
*
|
||||
* @param e
|
||||
* Respawn event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (respawned == null || isPlayerDisabled(respawned)) return;
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
|
||||
() -> getArmorStandManager(respawned).teleport());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
if (receiver.getVersion().getMinorVersion() < 8) return;
|
||||
if (!receiver.isLoaded() || getDisableChecker().isDisabledPlayer(receiver) || getUnlimitedDisableChecker().isDisabledPlayer(receiver)) return;
|
||||
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
|
||||
packetListener.onEntityMove((BackendTabPlayer) receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
|
||||
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
|
||||
packetListener.onEntityMove((BackendTabPlayer) receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
|
||||
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
|
||||
packetListener.onEntitySpawn((BackendTabPlayer) receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
|
||||
} else if (PacketPlayOutEntityDestroyStorage.CLASS.isInstance(packet)) {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
Object entities = PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet);
|
||||
if (entities instanceof List) {
|
||||
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (List<Integer>) entities);
|
||||
} else {
|
||||
//1.17.0
|
||||
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int) entities);
|
||||
}
|
||||
} else {
|
||||
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
|
||||
}
|
||||
}
|
||||
respawn(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,9 +93,10 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public @NotNull List<Integer> getPassengers(@NotNull Object entity) {
|
||||
@NotNull
|
||||
public List<Integer> getPassengers(@NotNull Object entity) {
|
||||
Entity vehicle = (Entity) entity;
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
|
||||
if (BukkitReflection.getMinorVersion() >= MULTI_PASSENGER_VERSION) {
|
||||
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
|
||||
} else {
|
||||
if (vehicle.getPassenger() != null) {
|
||||
@@ -125,7 +108,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getVehicle(@NotNull TabPlayer player) {
|
||||
@Nullable
|
||||
public Object getVehicle(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getVehicle();
|
||||
}
|
||||
|
||||
@@ -135,8 +119,9 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getEntityType(@NotNull Object entity) {
|
||||
return ((Entity) entity).getType().toString().toLowerCase();
|
||||
@NotNull
|
||||
public String getEntityType(@NotNull Object entity) {
|
||||
return ((Entity) entity).getType().toString().toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -147,13 +132,13 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
|
||||
@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();
|
||||
if (BukkitReflection.getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
|
||||
return BukkitReflection.getMinorVersion() == 13 && p.isSwimming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGliding(@NotNull TabPlayer player) {
|
||||
return TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
|
||||
return BukkitReflection.getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,7 +147,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Object getArmorStandType() {
|
||||
@NotNull
|
||||
public Object getArmorStandType() {
|
||||
return EntityType.ARMOR_STAND;
|
||||
}
|
||||
|
||||
@@ -182,12 +168,28 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
|
||||
DataWatcher datawatcher = new DataWatcher();
|
||||
datawatcher.getHelper().setEntityFlags(flags);
|
||||
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
|
||||
datawatcher.getHelper().setCustomNameVisible(nameVisible);
|
||||
datawatcher.getHelper().setArmorStandFlags((byte)16);
|
||||
datawatcher.setEntityFlags(flags);
|
||||
datawatcher.setCustomName(displayName, viewer.getVersion());
|
||||
datawatcher.setCustomNameVisible(nameVisible);
|
||||
datawatcher.setArmorStandFlags(datawatcher.MARKER_FLAG);
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFlying(@NotNull TabPlayer player) {
|
||||
return ((BukkitTabPlayer)player).getPlayer().isFlying();
|
||||
}
|
||||
}
|
||||
+73
-10
@@ -7,13 +7,12 @@ 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.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TAB's expansion for PlaceholderAPI
|
||||
@@ -22,11 +21,36 @@ import java.util.WeakHashMap;
|
||||
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
|
||||
|
||||
/** Map holding all values for all players for easy and high-performance access */
|
||||
private final WeakHashMap<Player, Map<String, String>> values = new WeakHashMap<>();
|
||||
@NotNull
|
||||
private final WeakHashMap<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
|
||||
|
||||
private final String author = TabConstants.PLUGIN_AUTHOR;
|
||||
private final String identifier = TabConstants.PLUGIN_ID;
|
||||
private final String version = TabConstants.PLUGIN_VERSION;
|
||||
/** List of all placeholders offered by the plugin for command suggestions */
|
||||
@NotNull
|
||||
private final List<String> placeholders = Collections.unmodifiableList(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() {
|
||||
@@ -34,6 +58,31 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
}
|
||||
|
||||
@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) + "%";
|
||||
@@ -48,13 +97,27 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
return text;
|
||||
}
|
||||
if (identifier.startsWith("placeholder_")) {
|
||||
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TAB.getInstance().getPlaceholderManager());
|
||||
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring("placeholder_".length()) + "%", TAB.getInstance().getPlaceholderManager());
|
||||
}
|
||||
return values.get(player).get(identifier);
|
||||
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) player.getPlayer(), p -> new HashMap<>()).put(key, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
-19
@@ -5,8 +5,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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;
|
||||
@@ -27,47 +26,64 @@ import org.jetbrains.annotations.NotNull;
|
||||
* Per-world-PlayerList feature handler
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@RequiredArgsConstructor
|
||||
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");
|
||||
private final boolean allowBypass = config().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
private final List<String> ignoredWorlds = config().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
|
||||
private final Map<String, List<String>> sharedWorlds = config().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
|
||||
/** Plugin reference*/
|
||||
@NotNull private final JavaPlugin plugin;
|
||||
|
||||
@Getter private final String featureName = "Per world PlayerList";
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
|
||||
/**
|
||||
* 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 : Bukkit.getOnlinePlayers()) {
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
for (Player pl : BukkitUtils.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
}
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join event listener to synchronously perform hiding.
|
||||
*
|
||||
* @param e
|
||||
* Join event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
|
||||
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
* World change listener to synchronously perform player showing/hiding.
|
||||
*
|
||||
* @param e
|
||||
* World change event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
|
||||
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,7 +95,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
* Player to update
|
||||
*/
|
||||
private void checkPlayer(@NotNull Player p) {
|
||||
for (Player all : Bukkit.getOnlinePlayers()) {
|
||||
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);
|
||||
@@ -109,4 +125,10 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
}
|
||||
return viewerWorldGroup.equals(targetWorldGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getFeatureName() {
|
||||
return "Per world PlayerList";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -10,7 +11,6 @@ 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;
|
||||
@@ -24,19 +24,25 @@ import org.jetbrains.annotations.NotNull;
|
||||
@RequiredArgsConstructor
|
||||
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
|
||||
|
||||
/** Distance of the Wither in blocks */
|
||||
/** Distance of the wither in blocks */
|
||||
private static final int WITHER_DISTANCE = 60;
|
||||
|
||||
/** Reference to plugin for registering listener */
|
||||
private final @NotNull JavaPlugin plugin;
|
||||
/**
|
||||
* Constructs new instance and registers events
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance to register events
|
||||
*/
|
||||
public WitherBossBar(JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
|
||||
//when in a small window, it's about 100ms
|
||||
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
|
||||
featureName, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
getFeatureName(), TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
super.load();
|
||||
teleport();
|
||||
}
|
||||
@@ -48,10 +54,11 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
|
||||
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 loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
|
||||
if (!line.containsPlayer(p)) continue;
|
||||
Location eyeLocation = ((BukkitTabPlayer)p).getPlayer().getEyeLocation();
|
||||
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(WITHER_DISTANCE));
|
||||
if (loc.getY() < 1) loc.setY(1);
|
||||
((BackendTabPlayer)p).teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ(), 0, 0));
|
||||
((BackendTabPlayer)p).getEntityView().teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,13 +70,16 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning wither as respawn screen destroys all entities in client
|
||||
* Respawning wither as respawn screen destroys all entities in client.
|
||||
*
|
||||
* @param e
|
||||
* Respawn event
|
||||
*/
|
||||
@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,
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
|
||||
() -> detectBossBarsAndSend(player));
|
||||
}
|
||||
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package me.neznamy.tab.platforms.bukkit.hook;
|
||||
|
||||
import de.myzelyam.api.vanish.VanishAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* PremiumVanish hook for Bukkit.
|
||||
*/
|
||||
public class BukkitPremiumVanishHook extends PremiumVanishHook {
|
||||
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
return VanishAPI.canSee(((BukkitTabPlayer)viewer).getPlayer(), ((BukkitTabPlayer)target).getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished(@NotNull TabPlayer player) {
|
||||
return VanishAPI.isInvisible(((BukkitTabPlayer)player).getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -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,113 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Class containing main NMS-related information and methods.
|
||||
*/
|
||||
public class BukkitReflection {
|
||||
|
||||
/** CraftBukkit package */
|
||||
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
|
||||
|
||||
/** Server version data */
|
||||
private static final ServerVersion serverVersion = detectServerVersion();
|
||||
|
||||
/** 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 = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundBundlePacket");
|
||||
|
||||
/** Flag determining whether the server version is at least 1.20.2 or not */
|
||||
@Getter
|
||||
private static final boolean is1_20_2Plus = ReflectionUtils.classExists("net.minecraft.world.scores.DisplaySlot");
|
||||
|
||||
/** Flag determining whether the server version is at least 1.20.3 or not */
|
||||
@Getter
|
||||
private static final boolean is1_20_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundResetScorePacket");
|
||||
|
||||
private static ServerVersion detectServerVersion() {
|
||||
FunctionWithException<String, Class<?>> classFunction = name -> Class.forName("net.minecraft." + name);
|
||||
String[] array = Bukkit.getServer().getClass().getPackage().getName().split("\\.");
|
||||
int minorVersion;
|
||||
if (array.length > 3) {
|
||||
// Normal packaging
|
||||
String serverPackage = array[3];
|
||||
minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
if (minorVersion < 17) {
|
||||
ClassLoader loader = BukkitReflection.class.getClassLoader();
|
||||
classFunction = name -> loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
}
|
||||
} else {
|
||||
// Paper without CB relocation
|
||||
minorVersion = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]);
|
||||
}
|
||||
return new ServerVersion(classFunction, minorVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server's minor version, such as 20 for 1.20.4
|
||||
*
|
||||
* @return server's minor version
|
||||
*/
|
||||
public static int getMinorVersion() {
|
||||
return serverVersion.getMinorVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class with given potential names in same order. For 1.17+ it takes packaged class names
|
||||
* without "net.minecraft." prefix, for <1.17 it takes class name only.
|
||||
*
|
||||
* @param names
|
||||
* possible class names
|
||||
* @return class for specified names
|
||||
* @throws ClassNotFoundException
|
||||
* if class does not exist
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
|
||||
for (String name : names) {
|
||||
try {
|
||||
return serverVersion.getClass.apply(name);
|
||||
} catch (ClassNotFoundException | NullPointerException ignored) {
|
||||
// not the first class name in array
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns CraftBukkit class with given package and name.
|
||||
*
|
||||
* @param name
|
||||
* Package and name of the class
|
||||
* @return CraftBukkit class
|
||||
* @throws ClassNotFoundException
|
||||
* If class does not exist
|
||||
*/
|
||||
public static Class<?> getBukkitClass(@NotNull String name) throws ClassNotFoundException {
|
||||
return Class.forName(CRAFTBUKKIT_PACKAGE + "." + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class with server version information.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
private static class ServerVersion {
|
||||
|
||||
private final FunctionWithException<String, Class<?>> getClass;
|
||||
private final int minorVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
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.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Class for converting TAB component into NMS components (1.7+).
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class ComponentConverter {
|
||||
|
||||
/** Instance of the class */
|
||||
@Nullable
|
||||
public static ComponentConverter INSTANCE;
|
||||
|
||||
private final FunctionWithException<String, Object> newTextComponent;
|
||||
private final BiFunction<ChatModifier, Boolean, Object> convertModifier;
|
||||
|
||||
private final Class<?> ChatModifier = BukkitReflection.getClass("network.chat.ChatModifier", "ChatModifier");
|
||||
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("EnumChatFormat");
|
||||
private final Constructor<?> newChatModifier;
|
||||
private final Method ChatBaseComponent_addSibling;
|
||||
private final Field Component_modifier;
|
||||
private final List<Field> magicCodes = ReflectionUtils.getFields(ChatModifier, Boolean.class);
|
||||
|
||||
// 1.15-
|
||||
private Method ChatModifier_setColor;
|
||||
|
||||
// 1.16+
|
||||
private Method ChatHexColor_fromRGB;
|
||||
private Constructor<?> newMinecraftKey;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes, constructors and methods.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
private ComponentConverter() throws ReflectiveOperationException {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
Method IChatBaseComponent_b = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class);
|
||||
newTextComponent = text -> IChatBaseComponent_b.invoke(null, text);
|
||||
Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.IChatMutableComponent", "IChatMutableComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
|
||||
} else {
|
||||
Class<?> ChatComponentText = BukkitReflection.getClass("network.chat.ChatComponentText", "ChatComponentText");
|
||||
Constructor<?> newChatComponentText = ChatComponentText.getConstructor(String.class);
|
||||
newTextComponent = newChatComponentText::newInstance;
|
||||
Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.ChatBaseComponent", "ChatBaseComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatComponentText, IChatBaseComponent, IChatBaseComponent);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
Class<?> chatHexColor = BukkitReflection.getClass("network.chat.ChatHexColor", "ChatHexColor");
|
||||
Class<?> MinecraftKey = BukkitReflection.getClass("resources.MinecraftKey", "MinecraftKey");
|
||||
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ChatClickable", "ChatClickable");
|
||||
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.ChatHoverable", "ChatHoverable");
|
||||
newMinecraftKey = MinecraftKey.getConstructor(String.class);
|
||||
ChatHexColor_fromRGB = ReflectionUtils.getOnlyMethod(chatHexColor, chatHexColor, int.class);
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifier.getDeclaredConstructor(chatHexColor, Boolean.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, MinecraftKey));
|
||||
convertModifier = this::createModifierModern;
|
||||
} else {
|
||||
newChatModifier = ChatModifier.getConstructor();
|
||||
ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifier, ChatModifier, EnumChatFormat);
|
||||
convertModifier = (modifier, protocolVersion) -> createModifierLegacy(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TAB component to NMS component.
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param modern
|
||||
* Whether client supports RGB or not
|
||||
* @return Converted component
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object convert(@NotNull TabComponent component, boolean modern) {
|
||||
if (component instanceof SimpleComponent) return newTextComponent.apply(((SimpleComponent) component).getText());
|
||||
|
||||
StructuredComponent component1 = (StructuredComponent) component;
|
||||
Object nmsComponent = newTextComponent.apply(component1.getText());
|
||||
Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier(), modern));
|
||||
for (StructuredComponent extra : component1.getExtra()) {
|
||||
ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra, modern));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierModern(@NotNull ChatModifier modifier, boolean modern) {
|
||||
Object color = null;
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb());
|
||||
} else {
|
||||
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getLegacyColor().getRgb());
|
||||
}
|
||||
}
|
||||
return newChatModifier.newInstance(
|
||||
color,
|
||||
modifier.isBold(),
|
||||
modifier.isItalic(),
|
||||
modifier.isUnderlined(),
|
||||
modifier.isStrikethrough(),
|
||||
modifier.isObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
modifier.getFont() == null ? null : newMinecraftKey.newInstance(modifier.getFont())
|
||||
);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierLegacy(@NotNull ChatModifier modifier) {
|
||||
Object nmsModifier = newChatModifier.newInstance();
|
||||
if (modifier.getColor() != null) {
|
||||
ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, modifier.getColor().getLegacyColor().name()));
|
||||
}
|
||||
if (modifier.isBold()) magicCodes.get(0).set(nmsModifier, true);
|
||||
if (modifier.isItalic()) magicCodes.get(1).set(nmsModifier, true);
|
||||
if (modifier.isStrikethrough()) magicCodes.get(2).set(nmsModifier, true);
|
||||
if (modifier.isUnderlined()) magicCodes.get(3).set(nmsModifier, true);
|
||||
if (modifier.isObfuscated()) magicCodes.get(4).set(nmsModifier, true);
|
||||
return nmsModifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load component converter.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
INSTANCE = new ComponentConverter();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the component converter is available and throws an exception if not.
|
||||
*/
|
||||
public static void ensureAvailable() {
|
||||
if (INSTANCE == null) throw new IllegalStateException("Component converter is not available");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.util.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class for sending packets to players.
|
||||
*/
|
||||
public class PacketSender {
|
||||
|
||||
/** Function for sending packet */
|
||||
@NotNull
|
||||
private final BiConsumerWithException<Player, Object> send;
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load required classes, fields and methods.
|
||||
* If something fails, error is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something fails
|
||||
*/
|
||||
public PacketSender() throws ReflectiveOperationException {
|
||||
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet", "Packet");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl", "server.network.PlayerConnection", "PlayerConnection");
|
||||
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Method sendPacket;
|
||||
if (BukkitReflection.getMinorVersion() >= 7) {
|
||||
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
|
||||
} else {
|
||||
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
|
||||
}
|
||||
send = (player, packet) -> sendPacket.invoke(PLAYER_CONNECTION.get(getHandle.invoke(player)), packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends packet to specified player.
|
||||
* If something goes wrong, throws an exception.
|
||||
*
|
||||
* @param player
|
||||
* Player to send packet to
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void sendPacket(@NotNull Player player, @NotNull Object packet) {
|
||||
send.accept(player, packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class for retrieving ping of players.
|
||||
*/
|
||||
public class PingRetriever {
|
||||
|
||||
/** Ping getter function */
|
||||
private static FunctionWithException<Player, Integer> getPing;
|
||||
|
||||
/**
|
||||
* Attempts to load required classes, fields and methods and marks class as available.
|
||||
* If something fails, error message is printed and class is not marked as available.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
getPing = Player::getPing;
|
||||
} else {
|
||||
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Field PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
|
||||
getPing = player -> PING.getInt(getHandle.invoke(player));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getPing = p -> -1;
|
||||
BukkitUtils.compatibilityError(e, "getting player's ping", null, "%ping% returning -1");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player's ping. If ping getter is not available and fields
|
||||
* failed to load, returns {@code -1}. If an exception was throws by
|
||||
* reflective operation, it is re-thrown.
|
||||
*
|
||||
* @param player
|
||||
* Player to get ping of
|
||||
* @return Player's ping or {@code -1} if it failed
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static int getPing(@NotNull Player player) {
|
||||
return getPing.apply(player);
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.ToString;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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<?> CLASS;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
public static Method REGISTER;
|
||||
public static Method markDirty;
|
||||
public static Method packDirty;
|
||||
|
||||
/** Watched data */
|
||||
private final Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
|
||||
|
||||
/** Helper for easier data write */
|
||||
@Getter private final DataWatcherHelper helper = new DataWatcherHelper(this);
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @param nms
|
||||
* NMS storage reference
|
||||
*/
|
||||
public static void load(NMSStorage nms) {
|
||||
CONSTRUCTOR = ReflectionUtils.getOnlyConstructor(CLASS);
|
||||
if (nms.is1_19_3Plus()) {
|
||||
markDirty = ReflectionUtils.getOnlyMethod(CLASS, void.class, DataWatcherObject.CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value into data values
|
||||
*
|
||||
* @param type
|
||||
* type of value
|
||||
* @param value
|
||||
* value
|
||||
*/
|
||||
public void setValue(@NotNull DataWatcherObject type, @NotNull Object value) {
|
||||
dataValues.put(type.getPosition(), new DataWatcherItem(type, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the class into an instance of NMS.DataWatcher
|
||||
*
|
||||
* @return an instance of NMS.DataWatcher with same data
|
||||
*/
|
||||
@SneakyThrows
|
||||
public @NotNull Object build() {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object nmsWatcher;
|
||||
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
|
||||
nmsWatcher = CONSTRUCTOR.newInstance(new Object[] {null});
|
||||
} else {
|
||||
nmsWatcher = CONSTRUCTOR.newInstance();
|
||||
}
|
||||
for (DataWatcherItem item : dataValues.values()) {
|
||||
Object nmsObject = item.getType().build();
|
||||
REGISTER.invoke(nmsWatcher, nmsObject, item.getValue());
|
||||
if (nms.is1_19_3Plus()) markDirty.invoke(nmsWatcher, nmsObject);
|
||||
}
|
||||
return nmsWatcher;
|
||||
}
|
||||
}
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A class to help to assign DataWatcher items as positions often change per-version
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class DataWatcherHelper {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> DataWatcherRegistry;
|
||||
public static Class<?> DataWatcherSerializer;
|
||||
public static Object DataWatcherSerializer_BYTE;
|
||||
public static Object DataWatcherSerializer_FLOAT;
|
||||
public static Object DataWatcherSerializer_STRING;
|
||||
public static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
|
||||
public static Object DataWatcherSerializer_BOOLEAN;
|
||||
|
||||
/** Instance fields */
|
||||
private final int armorStandFlagsPosition = getArmorStandFlagsPosition();
|
||||
@NotNull private final DataWatcher data;
|
||||
|
||||
/**
|
||||
* Returns armor stand flags position based on server version
|
||||
*
|
||||
* @return armor stand flags position based on server version
|
||||
*/
|
||||
private int getArmorStandFlagsPosition() {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
|
||||
//1.17.x, 1.18.x, 1.19.x
|
||||
return 15;
|
||||
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
|
||||
//1.15.x, 1.16.x
|
||||
return 14;
|
||||
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14) {
|
||||
//1.14.x
|
||||
return 13;
|
||||
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 10) {
|
||||
//1.10.x - 1.13.x
|
||||
return 11;
|
||||
} else {
|
||||
//1.8.x - 1.9.x
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity byte flags
|
||||
*
|
||||
* @param flags
|
||||
* flags to write
|
||||
*/
|
||||
public void setEntityFlags(byte flags) {
|
||||
data.setValue(new DataWatcherObject(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 (TAB.getInstance().getServerVersion().getMinorVersion() >= 13) {
|
||||
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_OPTIONAL_COMPONENT),
|
||||
Optional.ofNullable(NMSStorage.getInstance().toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
|
||||
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8) {
|
||||
data.setValue(new DataWatcherObject(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 (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
|
||||
data.setValue(new DataWatcherObject(10, null), cutName);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(5, null), cutName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes custom name visibility boolean
|
||||
*
|
||||
* @param visible
|
||||
* if visible or not
|
||||
*/
|
||||
public void setCustomNameVisible(boolean visible) {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
|
||||
data.setValue(new DataWatcherObject(3, DataWatcherSerializer_BOOLEAN), visible);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(3, null), (byte)(visible?1:0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity health
|
||||
*
|
||||
* @param health
|
||||
* health of entity
|
||||
*/
|
||||
public void setHealth(float health) {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
|
||||
data.setValue(new DataWatcherObject(6, DataWatcherSerializer_FLOAT), health);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(16, null), (int)health);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes armor stand flags
|
||||
*
|
||||
* @param flags
|
||||
* flags to write
|
||||
*/
|
||||
public void setArmorStandFlags(byte flags) {
|
||||
data.setValue(new DataWatcherObject(armorStandFlagsPosition, DataWatcherSerializer_BYTE), flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes wither invulnerable time
|
||||
* @param time
|
||||
* Time, apparently
|
||||
*/
|
||||
public void setWitherInvulnerableTime(int time) {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() > 8)
|
||||
throw new UnsupportedOperationException("Not supported on 1.9+");
|
||||
data.setValue(new DataWatcherObject(20, null), time);
|
||||
}
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import lombok.Data;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Class representing NMS Data Watcher Item
|
||||
*/
|
||||
@Data
|
||||
public class DataWatcherItem {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Field TYPE;
|
||||
public static Field VALUE;
|
||||
|
||||
/** Instance fields */
|
||||
@NotNull private final DataWatcherObject type;
|
||||
@NotNull private final Object value;
|
||||
|
||||
/**
|
||||
* Loads all required Fields
|
||||
*
|
||||
* @param nms
|
||||
* NMS storage reference
|
||||
*/
|
||||
public static void load(NMSStorage nms) {
|
||||
VALUE = ReflectionUtils.getFields(CLASS, Object.class).get(0);
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
TYPE = ReflectionUtils.getOnlyField(CLASS, DataWatcherObject.CLASS);
|
||||
} else {
|
||||
TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcherObject class to make work with it much easier
|
||||
*/
|
||||
@Data @AllArgsConstructor
|
||||
public class DataWatcherObject {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
public static Field SLOT;
|
||||
public static Field SERIALIZER;
|
||||
|
||||
/** Instance fields */
|
||||
private final int position;
|
||||
@Nullable private final Object serializer;
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @throws NoSuchMethodException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load() throws NoSuchMethodException {
|
||||
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcherHelper.DataWatcherSerializer);
|
||||
SLOT = ReflectionUtils.getOnlyField(CLASS, int.class);
|
||||
SERIALIZER = ReflectionUtils.getOnlyField(CLASS, DataWatcherHelper.DataWatcherSerializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the object into NMS object
|
||||
*
|
||||
* @return NMS object
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object build() {
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
|
||||
return DataWatcherObject.CONSTRUCTOR.newInstance(position, serializer);
|
||||
} else {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* NMS loader for Minecraft 1.5.2 - 1.16.5 using Bukkit mapping.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class BukkitLegacyNMSStorage extends NMSStorage {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private 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
|
||||
*
|
||||
* @param name
|
||||
* class name
|
||||
* @return class from given name
|
||||
* @throws ClassNotFoundException
|
||||
* if class was not found
|
||||
*/
|
||||
public Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
|
||||
return Class.forName("net.minecraft.server." + serverPackage + "." + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
|
||||
PacketScoreboard.ScoreboardScore_setScore = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardScoreClass, new String[] {"setScore", "c"}, int.class); // 1.5.1+, 1.5
|
||||
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setAllowFriendlyFire", "a"}, boolean.class); // 1.5.1+, 1.5
|
||||
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setCanSeeFriendlyInvisibles", "b"}, boolean.class); // 1.5.1+, 1.5
|
||||
if (minorVersion >= 7) {
|
||||
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setNameTagVisibility", "a"}, PacketScoreboard.EnumNameTagVisibility); // {1.8.1+, 1.8}
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("register", DataWatcherObject.CLASS, Object.class);
|
||||
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("a").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("c").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
|
||||
if (getMinorVersion() >= 13) {
|
||||
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
|
||||
} else {
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("h").get(null);
|
||||
}
|
||||
} else {
|
||||
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("a", int.class, Object.class);
|
||||
}
|
||||
if (minorVersion >= 13) {
|
||||
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("setPrefix", IChatBaseComponent);
|
||||
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("setSuffix", IChatBaseComponent);
|
||||
} else {
|
||||
PacketScoreboard.ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setPrefix", "b"}, String.class); // 1.5.1+, 1.5
|
||||
PacketScoreboard.ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setSuffix", "c"}, String.class); // 1.5.1+, 1.5
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadClasses() throws ClassNotFoundException {
|
||||
EntityHuman = getLegacyClass("EntityHuman");
|
||||
World = getLegacyClass("World");
|
||||
Packet = getLegacyClass("Packet");
|
||||
EnumChatFormat = (Class<Enum>) getLegacyClass("EnumChatFormat");
|
||||
EntityPlayer = getLegacyClass("EntityPlayer");
|
||||
Entity = getLegacyClass("Entity");
|
||||
EntityLiving = getLegacyClass("EntityLiving");
|
||||
PlayerConnection = getLegacyClass("PlayerConnection");
|
||||
NetworkManager = getLegacyClass("NetworkManager");
|
||||
DataWatcher.CLASS = getLegacyClass("DataWatcher");
|
||||
DataWatcherItem.CLASS = getLegacyClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
|
||||
PacketPlayOutSpawnEntityLivingStorage.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
|
||||
PacketPlayOutEntityTeleportStorage.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
|
||||
PacketPlayOutEntity = getLegacyClass("PacketPlayOutEntity", "Packet30Entity");
|
||||
PacketPlayOutEntityDestroyStorage.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
|
||||
PacketPlayOutEntityLook = getLegacyClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
|
||||
PacketPlayOutEntityMetadataStorage.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
|
||||
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
|
||||
PacketScoreboard.DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
|
||||
PacketScoreboard.ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
|
||||
PacketScoreboard.TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
|
||||
PacketScoreboard.ScorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
|
||||
PacketScoreboard.Scoreboard = getLegacyClass("Scoreboard");
|
||||
PacketScoreboard.ScoreboardObjective = getLegacyClass("ScoreboardObjective");
|
||||
PacketScoreboard.ScoreboardScoreClass = getLegacyClass("ScoreboardScore");
|
||||
PacketScoreboard.IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
|
||||
PacketScoreboard.ScoreboardTeam = getLegacyClass("ScoreboardTeam");
|
||||
if (minorVersion >= 7) {
|
||||
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
|
||||
ChatSerializer = getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
|
||||
EntityArmorStand = getLegacyClass("EntityArmorStand");
|
||||
BukkitTabList.PacketPlayOutPlayerInfoClass = getLegacyClass("PacketPlayOutPlayerInfo");
|
||||
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
|
||||
BukkitTabList.PlayerInfoDataClass = getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
|
||||
BukkitTabList.EnumGamemodeClass = (Class<Enum>) getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
|
||||
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
|
||||
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
|
||||
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcherObject.CLASS = getLegacyClass("DataWatcherObject");
|
||||
DataWatcherHelper.DataWatcherRegistry = getLegacyClass("DataWatcherRegistry");
|
||||
DataWatcherHelper.DataWatcherSerializer = getLegacyClass("DataWatcherSerializer");
|
||||
PacketScoreboard.EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
|
||||
}
|
||||
}
|
||||
}
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* NMS loader for minecraft 1.17+ using Mojang packaging and bukkit names.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class BukkitModernNMSStorage extends NMSStorage {
|
||||
|
||||
@Override
|
||||
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
|
||||
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
|
||||
DataWatcher.REGISTER = ReflectionUtils.getMethod(DataWatcher.CLASS, new String[]{"register", "a"}, DataWatcherObject.CLASS, Object.class); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardScore_setScore = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardScoreClass, new String[]{"setScore", "b"}, int.class); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setAllowFriendlyFire", "a"}, boolean.class); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setCanSeeFriendlyInvisibles", "b"}, boolean.class); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setPrefix", "b"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setSuffix", "c"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setNameTagVisibility", "a"}, PacketScoreboard.EnumNameTagVisibility); // {Bukkit, Bukkit 1.18+}
|
||||
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("a").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("c").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
|
||||
if (is1_19_3Plus()) {
|
||||
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("g").get(null);
|
||||
if (is1_19_4Plus()) {
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("k").get(null);
|
||||
} else {
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("j").get(null);
|
||||
}
|
||||
} else {
|
||||
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
|
||||
}
|
||||
if (minorVersion >= 19) {
|
||||
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("d").get(null);
|
||||
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("e")).setAccessible(true);
|
||||
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("b");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadClasses() throws ClassNotFoundException {
|
||||
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
|
||||
World = Class.forName("net.minecraft.world.level.World");
|
||||
EntityArmorStand = Class.forName("net.minecraft.world.entity.decoration.EntityArmorStand");
|
||||
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
|
||||
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
|
||||
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
|
||||
Packet = Class.forName("net.minecraft.network.protocol.Packet");
|
||||
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
|
||||
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
|
||||
Entity = Class.forName("net.minecraft.world.entity.Entity");
|
||||
EntityLiving = Class.forName("net.minecraft.world.entity.EntityLiving");
|
||||
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
|
||||
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
|
||||
PacketScoreboard.DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
|
||||
PacketScoreboard.ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
|
||||
PacketScoreboard.Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
|
||||
PacketScoreboard.ScorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
|
||||
PacketScoreboard.ScoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
|
||||
PacketScoreboard.ScoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
|
||||
PacketScoreboard.IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
|
||||
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
|
||||
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
|
||||
PacketScoreboard.TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
|
||||
PacketScoreboard.ScoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
|
||||
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
|
||||
PacketScoreboard.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
|
||||
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher");
|
||||
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher$Item");
|
||||
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
|
||||
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
|
||||
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
|
||||
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
|
||||
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity");
|
||||
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
|
||||
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook");
|
||||
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
|
||||
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn");
|
||||
BukkitTabList.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
|
||||
if (minorVersion >= 19) {
|
||||
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
|
||||
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
|
||||
} else {
|
||||
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving");
|
||||
}
|
||||
if (is1_19_3Plus()) {
|
||||
BukkitTabList.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
|
||||
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
|
||||
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
|
||||
BukkitTabList.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
|
||||
} else {
|
||||
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
|
||||
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
|
||||
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
|
||||
}
|
||||
}
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
|
||||
/**
|
||||
* NMS loader for minecraft 1.17+ using Mojang packaging and names.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class MojangModernNMSStorage extends NMSStorage {
|
||||
|
||||
@Override
|
||||
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
|
||||
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("fromJson", String.class);
|
||||
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("define", DataWatcherObject.CLASS, Object.class);
|
||||
PacketScoreboard.ScoreboardScore_setScore = PacketScoreboard.ScoreboardScoreClass.getMethod("setScore", int.class);
|
||||
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = PacketScoreboard.ScoreboardTeam.getMethod("setAllowFriendlyFire", boolean.class);
|
||||
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = PacketScoreboard.ScoreboardTeam.getMethod("setSeeFriendlyInvisibles", boolean.class);
|
||||
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("setPlayerPrefix", IChatBaseComponent);
|
||||
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("setPlayerSuffix", IChatBaseComponent);
|
||||
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = PacketScoreboard.ScoreboardTeam.getMethod("setNameTagVisibility", PacketScoreboard.EnumNameTagVisibility);
|
||||
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BYTE").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("FLOAT").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("STRING").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("OPTIONAL_COMPONENT").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BOOLEAN").get(null);
|
||||
if (minorVersion >= 19) {
|
||||
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
|
||||
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("type")).setAccessible(true);
|
||||
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("packDirty");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadClasses() throws ClassNotFoundException {
|
||||
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
|
||||
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
|
||||
World = Class.forName("net.minecraft.world.level.Level");
|
||||
EntityArmorStand = Class.forName("net.minecraft.world.entity.decoration.ArmorStand");
|
||||
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
|
||||
NetworkManager = Class.forName("net.minecraft.network.Connection");
|
||||
Packet = Class.forName("net.minecraft.network.protocol.Packet");
|
||||
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
|
||||
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
|
||||
Entity = Class.forName("net.minecraft.world.entity.Entity");
|
||||
EntityLiving = Class.forName("net.minecraft.world.entity.LivingEntity");
|
||||
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
|
||||
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
|
||||
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
|
||||
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData$DataItem");
|
||||
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
|
||||
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
|
||||
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
|
||||
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket");
|
||||
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket");
|
||||
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket");
|
||||
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot");
|
||||
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket");
|
||||
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket");
|
||||
BukkitTabList.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
|
||||
PacketScoreboard.DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
|
||||
PacketScoreboard.ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
|
||||
PacketScoreboard.Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
|
||||
PacketScoreboard.ScorePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
|
||||
PacketScoreboard.ScoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
|
||||
PacketScoreboard.ScoreboardScoreClass = Class.forName("net.minecraft.world.scores.Score");
|
||||
PacketScoreboard.IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
|
||||
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
|
||||
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
|
||||
PacketScoreboard.TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
|
||||
PacketScoreboard.ScoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
|
||||
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
|
||||
PacketScoreboard.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
|
||||
if (minorVersion >= 19) {
|
||||
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
|
||||
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket");
|
||||
} else {
|
||||
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddMobPacket");
|
||||
}
|
||||
if (is1_19_3Plus()) {
|
||||
BukkitTabList.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
|
||||
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
|
||||
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
|
||||
BukkitTabList.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
|
||||
} else {
|
||||
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
|
||||
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
|
||||
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ComponentCache;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class holding all NMS classes, methods, fields and constructors used by TAB.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public abstract class NMSStorage {
|
||||
|
||||
/** Instance of this class */
|
||||
@Getter @Setter private static NMSStorage instance;
|
||||
|
||||
/** Server's NMS/CraftBukkit package */
|
||||
@Getter protected final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
|
||||
/** Server's minor version */
|
||||
@Getter protected final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
|
||||
/** Flag determining whether the server version is at least 1.19.3 or not */
|
||||
@Getter private final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
@Getter private final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
|
||||
|
||||
/** Basic universal values */
|
||||
protected Class<?> Packet;
|
||||
protected Class<?> NetworkManager;
|
||||
protected Class<?> EntityArmorStand;
|
||||
protected Class<?> World;
|
||||
public Class<Enum> EnumChatFormat;
|
||||
public Class<?> EntityPlayer;
|
||||
protected Class<?> EntityHuman;
|
||||
public Class<?> Entity;
|
||||
public Class<?> EntityLiving;
|
||||
protected Class<?> PlayerConnection;
|
||||
|
||||
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
|
||||
public Field PING;
|
||||
|
||||
public Field PLAYER_CONNECTION;
|
||||
public Field NETWORK_MANAGER;
|
||||
public Field CHANNEL;
|
||||
public Method getHandle;
|
||||
public Method sendPacket;
|
||||
public Method getProfile;
|
||||
|
||||
/** Chat components */
|
||||
public Class<?> IChatBaseComponent;
|
||||
protected Class<?> ChatSerializer;
|
||||
public Method ChatSerializer_DESERIALIZE;
|
||||
|
||||
/** Other entity packets */
|
||||
public Class<?> PacketPlayOutEntity;
|
||||
public Field PacketPlayOutEntity_ENTITYID;
|
||||
|
||||
public Class<?> PacketPlayOutEntityLook;
|
||||
|
||||
public Class<?> PacketPlayOutNamedEntitySpawn;
|
||||
public Field PacketPlayOutNamedEntitySpawn_ENTITYID;
|
||||
|
||||
public Object dummyEntity;
|
||||
|
||||
private final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(10000,
|
||||
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load all fields, methods and constructors.
|
||||
*/
|
||||
@SneakyThrows
|
||||
public NMSStorage() {
|
||||
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
|
||||
loadClasses();
|
||||
if (minorVersion >= 7) {
|
||||
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) {
|
||||
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
|
||||
Constructor<?> newEntityArmorStand = EntityArmorStand.getConstructor(World, double.class, double.class, double.class);
|
||||
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".CraftWorld").getMethod("getHandle");
|
||||
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
|
||||
BukkitTabList.load(this);
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcherObject.load();
|
||||
}
|
||||
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
|
||||
DataWatcher.load(this);
|
||||
DataWatcherItem.load(this);
|
||||
PacketPlayOutEntityDestroyStorage.load();
|
||||
PacketPlayOutEntityMetadataStorage.load(this);
|
||||
PacketPlayOutEntityTeleportStorage.load(this);
|
||||
PacketPlayOutSpawnEntityLivingStorage.load(this);
|
||||
PacketScoreboard.load(this);
|
||||
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
|
||||
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
|
||||
loadNamedFieldsAndMethods();
|
||||
if (minorVersion < 17) {
|
||||
try {
|
||||
(PING = EntityPlayer.getDeclaredField("ping")).setAccessible(true); // 1.5.2 - 1.16.5
|
||||
} catch (NoSuchFieldException e) {
|
||||
(PING = EntityPlayer.getDeclaredField("field_71138_i")).setAccessible(true); // 1.7.10 Thermos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all classes used by the plugin
|
||||
*
|
||||
* @throws ClassNotFoundException
|
||||
* If a class was not found
|
||||
*/
|
||||
public abstract void loadClasses() throws ClassNotFoundException;
|
||||
|
||||
/**
|
||||
* Loads all fields and methods which must be loaded by their name due to
|
||||
* lack of other methods of reliably retrieving them.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If a field or method was not found
|
||||
*/
|
||||
public abstract void loadNamedFieldsAndMethods() throws ReflectiveOperationException;
|
||||
|
||||
/**
|
||||
* Converts TAB's IChatBaseComponent into minecraft's component using String deserialization.
|
||||
* If the requested component is found in cache, it is returned. If not, it is created, added into cache and returned.
|
||||
* If {@code component} is {@code null}, returns {@code null}
|
||||
*
|
||||
* @param component
|
||||
* component to convert
|
||||
* @param clientVersion
|
||||
* client version used to decide RGB conversion
|
||||
* @return converted component or {@code null} if {@code component} is {@code null}
|
||||
*/
|
||||
public @Nullable Object toNMSComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
|
||||
return componentCache.get(component, clientVersion);
|
||||
}
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* NMS loader for Thermos 1.7.10.
|
||||
*/
|
||||
public class ThermosNMSStorage extends BukkitLegacyNMSStorage {
|
||||
|
||||
@Override
|
||||
public Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
|
||||
try {
|
||||
return getClass().getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
} catch (NullPointerException e) {
|
||||
// nested class not found
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
|
||||
PacketScoreboard.ScoreboardScore_setScore = PacketScoreboard.ScoreboardScoreClass.getMethod("func_96647_c", int.class);
|
||||
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = PacketScoreboard.ScoreboardTeam.getMethod("func_96660_a", boolean.class);
|
||||
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = PacketScoreboard.ScoreboardTeam.getMethod("func_98300_b", boolean.class);
|
||||
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("func_75682_a", int.class, Object.class);
|
||||
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("func_96666_b", String.class);
|
||||
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("func_96662_c", String.class);
|
||||
}
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
|
||||
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutEntityDestroyStorage {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
public static Field ENTITIES;
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @throws NoSuchMethodException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load() throws NoSuchMethodException {
|
||||
ENTITIES = ReflectionUtils.getOnlyField(CLASS);
|
||||
try {
|
||||
CONSTRUCTOR = CLASS.getConstructor(int[].class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
//1.17.0
|
||||
CONSTRUCTOR = CLASS.getConstructor(int.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutEntityMetadataStorage {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @param nms
|
||||
* NMS storage reference
|
||||
* @throws NoSuchMethodException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load(NMSStorage nms) throws NoSuchMethodException {
|
||||
if (nms.is1_19_3Plus()) {
|
||||
CONSTRUCTOR = CLASS.getConstructor(int.class, List.class);
|
||||
} else {
|
||||
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcher.CLASS, boolean.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutEntityTeleportStorage {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
public static Field ENTITY_ID;
|
||||
public static Field X;
|
||||
public static Field Y;
|
||||
public static Field Z;
|
||||
public static Field YAW;
|
||||
public static Field PITCH;
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @param nms
|
||||
* NMS storage reference
|
||||
* @throws NoSuchMethodException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load(NMSStorage nms) throws NoSuchMethodException {
|
||||
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
|
||||
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
|
||||
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
CONSTRUCTOR = CLASS.getConstructor(nms.Entity);
|
||||
} else {
|
||||
CONSTRUCTOR = CLASS.getConstructor();
|
||||
}
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
|
||||
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
|
||||
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
|
||||
} else {
|
||||
X = ReflectionUtils.getFields(CLASS, int.class).get(1);
|
||||
Y = ReflectionUtils.getFields(CLASS, int.class).get(2);
|
||||
Z = ReflectionUtils.getFields(CLASS, int.class).get(3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this class into NMS packet
|
||||
*
|
||||
* @return NMS packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Object build(int entityId, Location location) {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object nmsPacket;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
|
||||
} else {
|
||||
nmsPacket = CONSTRUCTOR.newInstance();
|
||||
}
|
||||
ENTITY_ID.set(nmsPacket, entityId);
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
X.set(nmsPacket, location.getX());
|
||||
Y.set(nmsPacket, location.getY());
|
||||
Z.set(nmsPacket, location.getZ());
|
||||
} else {
|
||||
X.set(nmsPacket, floor(location.getX()*32));
|
||||
Y.set(nmsPacket, floor(location.getY()*32));
|
||||
Z.set(nmsPacket, floor(location.getZ()*32));
|
||||
}
|
||||
YAW.set(nmsPacket, (byte) (location.getYaw()/360*256));
|
||||
PITCH.set(nmsPacket, (byte) (location.getPitch()/360*256));
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
private static int floor(double paramDouble) {
|
||||
int i = (int)paramDouble;
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
}
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.EnumMap;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutSpawnEntityLivingStorage {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Class<?> EntityTypes;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
public static Field ENTITY_ID;
|
||||
public static Field ENTITY_TYPE;
|
||||
public static Field YAW;
|
||||
public static Field PITCH;
|
||||
public static Field UUID;
|
||||
public static Field X;
|
||||
public static Field Y;
|
||||
public static Field Z;
|
||||
public static Field DATA_WATCHER;
|
||||
public static Object EntityTypes_ARMOR_STAND;
|
||||
|
||||
/** Entity types */
|
||||
public static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @param nms
|
||||
* NMS storage reference
|
||||
* @throws NoSuchMethodException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load(NMSStorage nms) throws NoSuchMethodException {
|
||||
if (nms.getMinorVersion() >= 19) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 2);
|
||||
} else if (nms.getMinorVersion() >= 13) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 1);
|
||||
} else {
|
||||
entityIds.put(EntityType.WITHER, 64);
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 30);
|
||||
}
|
||||
}
|
||||
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
CONSTRUCTOR = CLASS.getConstructor(nms.is1_19_3Plus() ? nms.Entity : nms.EntityLiving);
|
||||
} else {
|
||||
CONSTRUCTOR = CLASS.getConstructor();
|
||||
}
|
||||
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
|
||||
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
|
||||
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
UUID = ReflectionUtils.getOnlyField(CLASS, UUID.class);
|
||||
if (nms.getMinorVersion() >= 19) {
|
||||
X = ReflectionUtils.getFields(CLASS, double.class).get(2);
|
||||
Y = ReflectionUtils.getFields(CLASS, double.class).get(3);
|
||||
Z = ReflectionUtils.getFields(CLASS, double.class).get(4);
|
||||
} else {
|
||||
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
|
||||
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
|
||||
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
|
||||
}
|
||||
} else {
|
||||
X = ReflectionUtils.getFields(CLASS, int.class).get(2);
|
||||
Y = ReflectionUtils.getFields(CLASS, int.class).get(3);
|
||||
Z = ReflectionUtils.getFields(CLASS, int.class).get(4);
|
||||
}
|
||||
if (nms.getMinorVersion() < 19) {
|
||||
ENTITY_TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
|
||||
}
|
||||
if (nms.getMinorVersion() <= 14) {
|
||||
DATA_WATCHER = ReflectionUtils.getOnlyField(CLASS, DataWatcher.CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this class into NMS packet
|
||||
*
|
||||
* @return NMS packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Object build(int entityId, UUID id, Object entityType, Location location, EntityData data) {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object nmsPacket;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
|
||||
} else {
|
||||
nmsPacket = CONSTRUCTOR.newInstance();
|
||||
}
|
||||
ENTITY_ID.set(nmsPacket, entityId);
|
||||
YAW.set(nmsPacket, (byte)(location.getYaw() * 256.0f / 360.0f));
|
||||
PITCH.set(nmsPacket, (byte)(location.getPitch() * 256.0f / 360.0f));
|
||||
if (nms.getMinorVersion() <= 14) {
|
||||
DATA_WATCHER.set(nmsPacket, data.build());
|
||||
}
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
UUID.set(nmsPacket, id);
|
||||
X.set(nmsPacket, location.getX());
|
||||
Y.set(nmsPacket, location.getY());
|
||||
Z.set(nmsPacket, location.getZ());
|
||||
} else {
|
||||
X.set(nmsPacket, floor(location.getX()*32));
|
||||
Y.set(nmsPacket, floor(location.getY()*32));
|
||||
Z.set(nmsPacket, floor(location.getZ()*32));
|
||||
}
|
||||
if (nms.getMinorVersion() >= 19) {
|
||||
ENTITY_TYPE.set(nmsPacket, EntityTypes_ARMOR_STAND); // :(
|
||||
} else {
|
||||
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) entityType));
|
||||
}
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
private static int floor(double paramDouble) {
|
||||
int i = (int)paramDouble;
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
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.platforms.bukkit.entity.PacketEntityView;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
|
||||
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.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
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.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.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.placeholders.types.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;
|
||||
|
||||
/** Server version */
|
||||
@Getter
|
||||
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
|
||||
|
||||
/** 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");
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin
|
||||
*/
|
||||
public BukkitPlatform(@NotNull JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
long time = System.currentTimeMillis();
|
||||
try {
|
||||
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
spigotTps = server.getClass().getField("recentTps");
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
//not spigot
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PremiumVanish")) {
|
||||
PremiumVanishHook.setInstance(new BukkitPremiumVanishHook());
|
||||
}
|
||||
ComponentConverter.tryLoad();
|
||||
PacketEntityView.tryLoad();
|
||||
PingRetriever.tryLoad();
|
||||
TabListBase.findInstance();
|
||||
ScoreboardLoader.tryLoad();
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
BukkitPipelineInjector.tryLoad();
|
||||
}
|
||||
BukkitUtils.sendCompatibilityMessage();
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + EnumChatFormat.GRAY + "Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BossBarManagerImpl getBossBar() {
|
||||
if (BukkitReflection.getMinorVersion() <= 8) return new WitherBossBar(plugin);
|
||||
return new BossBarManagerImpl();
|
||||
}
|
||||
|
||||
@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();
|
||||
int refresh = TAB.getInstance().getConfiguration().getPermissionRefreshInterval();
|
||||
manager.registerPlayerPlaceholder("%vault-prefix%", refresh, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder("%vault-suffix%", refresh, 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 BukkitReflection.getMinorVersion() >= 8 && BukkitPipelineInjector.isAvailable() ? new BukkitPipelineInjector() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public NameTag getUnlimitedNameTags() {
|
||||
return BukkitReflection.getMinorVersion() >= 8 &&
|
||||
PacketEntityView.isAvailable() &&
|
||||
BukkitPipelineInjector.isAvailable() ?
|
||||
new BukkitNameTagX(plugin) : new NameTag();
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a sync placeholder with given identifier and refresh.
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Placeholder refresh
|
||||
*/
|
||||
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 TabComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + toBukkitFormat(message, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] [WARN] " + toBukkitFormat(message, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0];
|
||||
}
|
||||
|
||||
@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 SimpleComponent("Failed to register command, is it defined in plugin.yml?"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
Metrics metrics = new Metrics(plugin, TabConstants.BSTATS_PLUGIN_ID_BUKKIT);
|
||||
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." + BukkitReflection.getMinorVersion() + ".x"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertComponent(@NotNull TabComponent component, boolean modern) {
|
||||
if (ComponentConverter.INSTANCE != null) {
|
||||
return ComponentConverter.INSTANCE.convert(component, modern);
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs task in the main thread for given entity.
|
||||
*
|
||||
* @param entity
|
||||
* Entity's main thread
|
||||
* @param task
|
||||
* Task to run
|
||||
*/
|
||||
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
|
||||
Bukkit.getScheduler().runTask(plugin, task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an entity task that may or may not need to use its main thread.
|
||||
*
|
||||
* @param entity
|
||||
* Entity to work with
|
||||
* @param task
|
||||
* Task to run
|
||||
*/
|
||||
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
|
||||
task.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
if (BackendPlatform.super.canSee(viewer, target)) return true;
|
||||
return ((BukkitTabPlayer)viewer).getPlayer().canSee(((BukkitTabPlayer)target).getPlayer());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 String toBukkitFormat(@NotNull TabComponent component, boolean rgbClient) {
|
||||
if (component instanceof SimpleComponent) return component.toLegacyText();
|
||||
StructuredComponent iComponent = (StructuredComponent) component;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (iComponent.getModifier().getColor() != null) {
|
||||
if (serverVersion.supportsRGB() && rgbClient) {
|
||||
String hexCode = iComponent.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(iComponent.getModifier().getColor().getLegacyColor());
|
||||
}
|
||||
}
|
||||
sb.append(iComponent.getModifier().getMagicCodes());
|
||||
sb.append(iComponent.getText());
|
||||
for (StructuredComponent extra : iComponent.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra, rgbClient));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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.SimpleComponent;
|
||||
import me.neznamy.tab.shared.placeholders.types.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 {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin
|
||||
*/
|
||||
public FoliaPlatform(@NotNull JavaPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
super.loadPlayers();
|
||||
|
||||
// Values are never updated in the API, warn users
|
||||
logWarn(new SimpleComponent("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
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("JavaReflectionMemberAccess")
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Dummy implementation if no scoreboard implementation is available.
|
||||
*/
|
||||
public class NullScoreboard extends Scoreboard<BukkitTabPlayer, Object> {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NullScoreboard(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDisplaySlot0(int slot, @NonNull String objective) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
|
||||
@Nullable Object displayName, @Nullable Object numberFormat) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Object numberFormat) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterObjective0(@NonNull String objectiveName) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Object numberFormat) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterTeam0(@NonNull String name) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
-302
@@ -1,302 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation for Bukkit, which uses packets
|
||||
* to send scoreboards. While md_5 keeps saying that you don't
|
||||
* need packets for scoreboards in every single spigot update,
|
||||
* this is not true. It may seem that way at first, but after
|
||||
* investigating, fatal problems are found:<p>
|
||||
* #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>
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static NMSStorage nms;
|
||||
|
||||
public static Class<?> Scoreboard;
|
||||
public static Class<?> IScoreboardCriteria;
|
||||
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
|
||||
public static Class<?> ScorePacketClass;
|
||||
public static Class<?> ScoreboardScoreClass;
|
||||
public static Class<Enum> EnumScoreboardAction;
|
||||
private static Constructor<?> newScorePacket_1_13;
|
||||
private static Constructor<?> newScorePacket_String;
|
||||
private static Constructor<?> newScorePacket;
|
||||
private static Constructor<?> newScoreboardScore;
|
||||
public 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;
|
||||
public static Class<Enum> EnumScoreboardHealthDisplay;
|
||||
public static Class<?> ScoreboardObjective;
|
||||
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;
|
||||
public static Class<Enum> EnumNameTagVisibility;
|
||||
public static Class<Enum> EnumTeamPush;
|
||||
public static Class<?> ScoreboardTeam;
|
||||
private static Constructor<?> newScoreboardTeam;
|
||||
private static Method ScoreboardTeam_getPlayerNameSet;
|
||||
public static Method ScoreboardTeam_setNameTagVisibility;
|
||||
private static Method ScoreboardTeam_setCollisionRule;
|
||||
public static Method ScoreboardTeam_setPrefix;
|
||||
public static Method ScoreboardTeam_setSuffix;
|
||||
private static Method ScoreboardTeam_setColor;
|
||||
public static Method ScoreboardTeam_setAllowFriendlyFire;
|
||||
public static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
|
||||
|
||||
public static void load(NMSStorage nms) throws ReflectiveOperationException {
|
||||
PacketScoreboard.nms = nms;
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
IScoreboardCriteria_self = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
|
||||
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
|
||||
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 (nms.getMinorVersion() >= 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, nms.IChatBaseComponent);
|
||||
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, nms.EnumChatFormat);
|
||||
} else {
|
||||
newScorePacket_String = ScorePacketClass.getConstructor(String.class);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor();
|
||||
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass);
|
||||
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
|
||||
} else {
|
||||
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass, int.class);
|
||||
}
|
||||
}
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, EnumTeamPush);
|
||||
}
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam);
|
||||
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam, boolean.class);
|
||||
} else {
|
||||
newTeamPacket = TeamPacketClass.getConstructor(ScoreboardTeam, int.class);
|
||||
}
|
||||
}
|
||||
|
||||
public PacketScoreboard(BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
player.sendPacket(newDisplayObjective.newInstance(slot.ordinal(), newScoreboardObjective(objective)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
player.sendPacket(buildObjective(0, objectiveName, title, hearts));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
player.sendPacket(buildObjective(1, objectiveName, "", false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
player.sendPacket(buildObjective(2, objectiveName, title, hearts));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object buildObjective(int action, String objectiveName, String title, boolean hearts) {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return newObjectivePacket.newInstance(
|
||||
newScoreboardObjective.newInstance(null, objectiveName, null,
|
||||
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
|
||||
asDisplayType(hearts)),
|
||||
action
|
||||
);
|
||||
}
|
||||
Object nmsPacket = newObjectivePacket.newInstance();
|
||||
Objective_OBJECTIVE_NAME.set(nmsPacket, objectiveName);
|
||||
Objective_DISPLAY_NAME.set(nmsPacket, title);
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
Objective_RENDER_TYPE.set(nmsPacket, asDisplayType(hearts));
|
||||
}
|
||||
Objective_METHOD.set(nmsPacket, action);
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
private Object asDisplayType(boolean hearts) {
|
||||
return Enum.valueOf(EnumScoreboardHealthDisplay, hearts ? "HEARTS" : "INTEGER");
|
||||
}
|
||||
|
||||
@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 (nms.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 (nms.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 (nms.getMinorVersion() >= 17) {
|
||||
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
|
||||
} else {
|
||||
player.sendPacket(newTeamPacket.newInstance(team, 2));
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createTeam(String teamName, String prefix, String suffix, NameVisibility visibility, 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 (nms.getMinorVersion() >= 13) {
|
||||
ScoreboardTeam_setPrefix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
|
||||
ScoreboardTeam_setSuffix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
|
||||
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, EnumChatFormat.lastColorsOf(prefix).toString()));
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix.invoke(team, prefix);
|
||||
ScoreboardTeam_setSuffix.invoke(team, suffix);
|
||||
}
|
||||
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
|
||||
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
|
||||
return team;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
Object packet;
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
packet = newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score);
|
||||
} else {
|
||||
Object scoreboardScore = newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(objective), playerName);
|
||||
ScoreboardScore_setScore.invoke(scoreboardScore, score);
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
packet = newScorePacket.newInstance(scoreboardScore);
|
||||
} else {
|
||||
packet = newScorePacket.newInstance(scoreboardScore, 0);
|
||||
}
|
||||
}
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, playerName, 0));
|
||||
} else {
|
||||
player.sendPacket(newScorePacket_String.newInstance(playerName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Scoreboard Objective with given name.
|
||||
*
|
||||
* @param objectiveName
|
||||
* Objective name
|
||||
* @return NMS Objective
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object newScoreboardObjective(@NotNull String objectiveName) {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return newScoreboardObjective.newInstance(
|
||||
null,
|
||||
objectiveName,
|
||||
null,
|
||||
nms.toNMSComponent(new IChatBaseComponent(""), TAB.getInstance().getServerVersion()),
|
||||
null
|
||||
);
|
||||
}
|
||||
return newScoreboardObjective.newInstance(
|
||||
null,
|
||||
objectiveName,
|
||||
IScoreboardCriteria_self.get(null)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
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 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.
|
||||
* <p>
|
||||
* Currently, this class is never used. In the future, it might
|
||||
* be used as a fallback solution for unsupported versions if NMS
|
||||
* changes and fields fail to load. Or as a complete replacement
|
||||
* if compatibility with other plugins turns out not to be a problem.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions") // Throw the NPE if something is not as expected, parent class should ensure it anyway
|
||||
public class PaperScoreboard extends Scoreboard<BukkitTabPlayer> {
|
||||
|
||||
private final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
|
||||
|
||||
public PaperScoreboard(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();
|
||||
scoreboard.getObjective(objective).getScore(playerName).setScore(score);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.resetScores(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.registerNewObjective(objectiveName, "dummy", toAdventure(title), hearts ? RenderType.HEARTS : RenderType.INTEGER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getObjective(objectiveName).unregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
checkPlayerScoreboard();
|
||||
Objective obj = scoreboard.getObjective(objectiveName);
|
||||
obj.setRenderType(hearts ? RenderType.HEARTS : RenderType.INTEGER);
|
||||
obj.displayName(toAdventure(title));
|
||||
}
|
||||
|
||||
@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);
|
||||
team.prefix(toAdventure(prefix));
|
||||
team.suffix(toAdventure(suffix));
|
||||
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.values()[visibility.ordinal()]);
|
||||
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
|
||||
players.forEach(team::addEntry);
|
||||
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);
|
||||
team.prefix(toAdventure(prefix));
|
||||
team.suffix(toAdventure(suffix));
|
||||
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.values()[visibility.ordinal()]);
|
||||
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
|
||||
team.setAllowFriendlyFire((options & 0x01) != 0);
|
||||
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts raw text into adventure component
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @return Converted component
|
||||
*/
|
||||
private @NotNull Component toAdventure(@NotNull String text) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toAdventureComponent(player.getVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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.scoreboard.packet.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Loader finding the best available Scoreboard implementation.
|
||||
*/
|
||||
public class ScoreboardLoader {
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
@NotNull
|
||||
private static Function<BukkitTabPlayer, Scoreboard<BukkitTabPlayer, ?>> instance = NullScoreboard::new;
|
||||
|
||||
/**
|
||||
* Tries to load Scoreboard packets.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
if (PacketScoreboard.isAvailable()) {
|
||||
instance = PacketScoreboard::new;
|
||||
} else {
|
||||
BukkitUtils.compatibilityError(PacketScoreboard.getException(), "Scoreboards", null,
|
||||
"Scoreboard feature will not work",
|
||||
"Belowname feature will not work",
|
||||
"Player objective feature will not work",
|
||||
"Scoreboard teams feature will not work (nametags & sorting)");
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Class storing NMS fields and methods of DisplayObjective packet.
|
||||
*/
|
||||
public class DisplayPacketData {
|
||||
|
||||
private final Class<?> DisplayObjectiveClass;
|
||||
private final Constructor<?> newDisplayObjective;
|
||||
private final Field DisplayObjective_OBJECTIVE_NAME;
|
||||
private final Object[] displaySlots;
|
||||
private final FunctionWithException<Object, Integer> packetToSlot;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all required NMS fields.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something fails
|
||||
*/
|
||||
public DisplayPacketData() throws ReflectiveOperationException {
|
||||
Class<?> ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
|
||||
DisplayObjectiveClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetDisplayObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.7 - 1.16.5
|
||||
"Packet208SetScoreboardDisplayObjective" // Bukkit 1.5 - 1.6.4
|
||||
);
|
||||
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
Class<?> DisplaySlot = BukkitReflection.getClass("world.scores.DisplaySlot");
|
||||
displaySlots = (Object[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
|
||||
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, ScoreboardObjective);
|
||||
packetToSlot = packet -> ((Enum<?>)DisplayObjective_POSITION.get(packet)).ordinal();
|
||||
} else {
|
||||
displaySlots = new Object[]{0, 1, 2};
|
||||
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
|
||||
packetToSlot = DisplayObjective_POSITION::getInt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet for setting display slot.
|
||||
*
|
||||
* @param slot
|
||||
* Objective slot
|
||||
* @param objective
|
||||
* Objective to display
|
||||
* @return Packet for setting display slot
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object setDisplaySlot(int slot, @NonNull Object objective) {
|
||||
return newDisplayObjective.newInstance(displaySlots[slot], objective);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if packet is display objective packet and forwards it to enabled features.
|
||||
*
|
||||
* @param player
|
||||
* Player who received packet
|
||||
* @param packet
|
||||
* Received packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
|
||||
if (DisplayObjectiveClass.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packetToSlot.apply(packet),
|
||||
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
}
|
||||
}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
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.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation which uses packets
|
||||
* to send scoreboards to use the full potential on all versions
|
||||
* and server software without any artificial limits.
|
||||
*/
|
||||
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer, Object> {
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
@Getter
|
||||
private static Exception exception;
|
||||
|
||||
static Class<?> Component;
|
||||
static Class<?> Scoreboard;
|
||||
static Class<?> ScoreboardObjective;
|
||||
static Object emptyScoreboard;
|
||||
static Class<?> NumberFormat;
|
||||
private static Constructor<?> newFixedFormat;
|
||||
|
||||
// Objective packet
|
||||
private static Class<?> ObjectivePacketClass;
|
||||
private static Constructor<?> newObjectivePacket;
|
||||
private static Field Objective_OBJECTIVE_NAME;
|
||||
private static Field Objective_METHOD;
|
||||
private static Field Objective_RENDER_TYPE;
|
||||
static Constructor<?> newScoreboardObjective;
|
||||
private static Method ScoreboardObjective_setDisplayName;
|
||||
private static Enum<?>[] healthDisplays;
|
||||
static Object IScoreboardCriteria_dummy;
|
||||
|
||||
private static ScorePacketData scorePacketData;
|
||||
@Getter private static TeamPacketData teamPacketData;
|
||||
@Getter private static DisplayPacketData displayPacketData;
|
||||
private static PacketSender packetSender;
|
||||
|
||||
private final Map<String, Object> teams = new HashMap<>();
|
||||
|
||||
static {
|
||||
try {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
|
||||
Class<?> IScoreboardCriteria = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria", // Mojang mapped
|
||||
"world.scores.criteria.IScoreboardCriteria", // Bukkit 1.17.+
|
||||
"IScoreboardCriteria", // 1.5.1 - 1.16.5
|
||||
"IObjective" // 1.5.0
|
||||
);
|
||||
ObjectivePacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardObjective", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardObjective", // 1.7 - 1.16.5
|
||||
"Packet206SetScoreboardObjective" // 1.5 - 1.6.4
|
||||
);
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
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);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
|
||||
IScoreboardCriteria_dummy = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0).get(null);
|
||||
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
|
||||
if (minorVersion >= 7) {
|
||||
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
ComponentConverter.ensureAvailable();
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria$RenderType",
|
||||
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"EnumScoreboardHealthDisplay");
|
||||
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
|
||||
if (minorVersion < 13) {
|
||||
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
|
||||
}
|
||||
}
|
||||
if (minorVersion < 13) {
|
||||
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
|
||||
}
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
|
||||
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
|
||||
}
|
||||
scorePacketData = new ScorePacketData();
|
||||
teamPacketData = new TeamPacketData();
|
||||
displayPacketData = new DisplayPacketData();
|
||||
packetSender = new PacketSender();
|
||||
available = true;
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public PacketScoreboard(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot0(int slot, @NonNull String objective) {
|
||||
packetSender.sendPacket(player.getPlayer(), displayPacketData.setDisplaySlot(slot, newObjective(objective, "", 0, null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Object numberFormat) {
|
||||
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.REGISTER, objectiveName, title, display, numberFormat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NonNull String objectiveName) {
|
||||
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.UNREGISTER, objectiveName, "", 0, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Object numberFormat) {
|
||||
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.UPDATE, objectiveName, title, display, numberFormat));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object newObjectivePacket(int action, @NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Object numberFormat) {
|
||||
Object packet = newObjectivePacket.newInstance(newObjective(objectiveName, title, display, numberFormat), action);
|
||||
if (BukkitReflection.getMinorVersion() >= 8 && BukkitReflection.getMinorVersion() < 13) {
|
||||
Objective_RENDER_TYPE.set(packet, healthDisplays[display]);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
|
||||
Object team = teamPacketData.createTeam(name);
|
||||
teams.put(name, team);
|
||||
packetSender.sendPacket(player.getPlayer(), teamPacketData.registerTeam(team, prefix, toComponent(prefix), suffix,
|
||||
toComponent(suffix), visibility, collision, players, options, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NonNull String name) {
|
||||
packetSender.sendPacket(player.getPlayer(), teamPacketData.unregisterTeam(teams.remove(name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
packetSender.sendPacket(player.getPlayer(), teamPacketData.updateTeam(teams.get(name), prefix, toComponent(prefix), suffix,
|
||||
toComponent(suffix), visibility, collision, options, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
|
||||
@Nullable Object displayName, @Nullable Object numberFormat) {
|
||||
packetSender.sendPacket(player.getPlayer(), scorePacketData.setScore(objective, scoreHolder, score, displayName, toFixedFormat(numberFormat)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
|
||||
packetSender.sendPacket(player.getPlayer(), scorePacketData.removeScore(objective, scoreHolder));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
displayPacketData.onPacketSend(player, packet);
|
||||
if (ObjectivePacketClass.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
if (isAntiOverrideTeams()) teamPacketData.onPacketSend(player, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Scoreboard Objective with given parameters.
|
||||
*
|
||||
* @param objectiveName
|
||||
* Objective name
|
||||
* @param title
|
||||
* Objective title
|
||||
* @param renderType
|
||||
* Render type
|
||||
* @param numberFormat
|
||||
* Default number format (1.20.3+)
|
||||
* @return Created objective
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object newObjective(@NonNull String objectiveName, @NonNull String title, int renderType,
|
||||
@Nullable Object numberFormat) {
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
// 1.20.3+
|
||||
return newScoreboardObjective.newInstance(
|
||||
emptyScoreboard,
|
||||
objectiveName,
|
||||
null, // Criteria
|
||||
toComponent(title),
|
||||
healthDisplays[renderType],
|
||||
false, // Auto update
|
||||
toFixedFormat(numberFormat)
|
||||
);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
// 1.13 - 1.20.2
|
||||
return newScoreboardObjective.newInstance(
|
||||
emptyScoreboard,
|
||||
objectiveName,
|
||||
null, // Criteria
|
||||
toComponent(title),
|
||||
healthDisplays[renderType]
|
||||
);
|
||||
}
|
||||
// 1.5 - 1.12.2
|
||||
Object objective = newScoreboardObjective.newInstance(emptyScoreboard, objectiveName, IScoreboardCriteria_dummy);
|
||||
ScoreboardObjective_setDisplayName.invoke(objective, title);
|
||||
return objective;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Object toComponent(@NonNull String text) {
|
||||
return TabComponent.optimized(text).convert(player.getVersion());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
private Object toFixedFormat(@Nullable Object numberFormat) {
|
||||
if (numberFormat == null || newFixedFormat == null) return null;
|
||||
return newFixedFormat.newInstance(numberFormat);
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.util.BiFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.QuintFunction;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Class containing data for scoreboard score set/reset packets.
|
||||
*/
|
||||
public class ScorePacketData {
|
||||
|
||||
private final QuintFunction<String, String, Integer, Object, Object, Object> setScore;
|
||||
private final BiFunctionWithException<String, String, Object> removeScore;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all fields. Throws exception if something went wrong.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something went wrong
|
||||
*/
|
||||
public ScorePacketData() throws ReflectiveOperationException {
|
||||
Class<?> SetScorePacket = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetScorePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardScore", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardScore", // 1.7 - 1.16.5
|
||||
"Packet207SetScoreboardScore" // 1.5 - 1.6.4
|
||||
);
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
QuintFunction<String, String, Integer, Object, Object, Object> setScore0;
|
||||
try {
|
||||
// 1.20.5+
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, Optional.class, Optional.class);
|
||||
setScore0 = (objective, holder, score, displayName, numberFormat) ->
|
||||
newSetScore.newInstance(holder, objective, score, Optional.ofNullable(displayName), Optional.ofNullable(numberFormat));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
// 1.20.3 - 1.20.4
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, PacketScoreboard.Component, PacketScoreboard.NumberFormat);
|
||||
setScore0 = (objective, holder, score, displayName, numberFormat) ->
|
||||
newSetScore.newInstance(holder, objective, score, displayName, numberFormat);
|
||||
}
|
||||
setScore = setScore0;
|
||||
Constructor<?> newResetScore = BukkitReflection.getClass("network.protocol.game.ClientboundResetScorePacket").getConstructor(String.class, String.class);
|
||||
removeScore = (objective, holder) -> newResetScore.newInstance(holder, objective);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
Class<?> actionClass = BukkitReflection.getClass("server.ServerScoreboard$Method", "server.ScoreboardServer$Action", "ScoreboardServer$Action");
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(actionClass, String.class, String.class, int.class);
|
||||
Enum<?>[] scoreboardActions = (Enum<?>[]) actionClass.getMethod("values").invoke(null);
|
||||
setScore = (objective, holder, score, displayName, numberFormat) -> newSetScore.newInstance(scoreboardActions[0], objective, holder, score);
|
||||
removeScore = (objective, holder) -> newSetScore.newInstance(scoreboardActions[1], objective, holder, 0);
|
||||
} else {
|
||||
Class<?> ScoreboardScore = BukkitReflection.getClass("ScoreboardScore");
|
||||
Constructor<?> newResetScore = SetScorePacket.getConstructor(String.class);
|
||||
Field SetScorePacket_SCORE = ReflectionUtils.getFields(SetScorePacket, int.class).get(0);
|
||||
Constructor<?> newScoreboardScore = ScoreboardScore.getConstructor(PacketScoreboard.Scoreboard, PacketScoreboard.ScoreboardObjective, String.class);
|
||||
Constructor<?> newSetScore;
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
newSetScore = SetScorePacket.getConstructor(ScoreboardScore);
|
||||
} else {
|
||||
newSetScore = SetScorePacket.getConstructor(ScoreboardScore, int.class);
|
||||
}
|
||||
setScore = (objective, holder, score, displayName, numberFormat) -> {
|
||||
Object scoreboardScore = newScoreboardScore.newInstance(
|
||||
PacketScoreboard.emptyScoreboard,
|
||||
PacketScoreboard.newScoreboardObjective.newInstance(PacketScoreboard.emptyScoreboard, objective, PacketScoreboard.IScoreboardCriteria_dummy),
|
||||
holder
|
||||
);
|
||||
Object packet;
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
packet = newSetScore.newInstance(scoreboardScore);
|
||||
} else {
|
||||
packet = newSetScore.newInstance(scoreboardScore, Scoreboard.ScoreAction.CHANGE);
|
||||
}
|
||||
SetScorePacket_SCORE.set(packet, score);
|
||||
return packet;
|
||||
};
|
||||
removeScore = (objective, holder) -> newResetScore.newInstance(holder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates set score packet.
|
||||
*
|
||||
* @param objective
|
||||
* Objective name
|
||||
* @param scoreHolder
|
||||
* Score holder
|
||||
* @param score
|
||||
* Score value
|
||||
* @param displayName
|
||||
* Score holder display name
|
||||
* @param numberFormat
|
||||
* Score value number format
|
||||
* @return Set score packet with given parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object setScore(@NonNull String objective, @NonNull String scoreHolder, int score,
|
||||
@Nullable Object displayName, @Nullable Object numberFormat) {
|
||||
return setScore.apply(objective, scoreHolder, score, displayName, numberFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates reset score packet.
|
||||
*
|
||||
* @param objective
|
||||
* Objective name
|
||||
* @param scoreHolder
|
||||
* Score holder
|
||||
* @return Reset score packet with given parameters.
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object removeScore(@NonNull String objective, @NonNull String scoreHolder) {
|
||||
return removeScore.apply(objective, scoreHolder);
|
||||
}
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard.TeamAction;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Class storing all team related fields and methods.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class TeamPacketData {
|
||||
|
||||
/** First version with modern team data using components */
|
||||
private final int MODERN_TEAM_DATA_VERSION = 13;
|
||||
|
||||
/** First version with static constructor-like methods */
|
||||
private final int STATIC_CONSTRUCTOR_VERSION = 17;
|
||||
|
||||
private Class<?> Component;
|
||||
private final Object emptyScoreboard;
|
||||
@Getter private final Class<?> TeamPacketClass;
|
||||
private Constructor<?> newTeamPacket;
|
||||
private final Constructor<?> newScoreboardTeam;
|
||||
private Method TeamPacketConstructor_of;
|
||||
private Method TeamPacketConstructor_ofBoolean;
|
||||
private final Field TeamPacket_NAME;
|
||||
private final Field TeamPacket_ACTION;
|
||||
private final Field TeamPacket_PLAYERS;
|
||||
private final Method ScoreboardTeam_getPlayerNameSet;
|
||||
private final Method ScoreboardTeam_setPrefix;
|
||||
private final Method ScoreboardTeam_setSuffix;
|
||||
private Method ScoreboardTeam_setColor;
|
||||
private final Method ScoreboardTeam_setAllowFriendlyFire;
|
||||
private final Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
|
||||
private final Enum<?>[] chatFormats;
|
||||
|
||||
private BiConsumerWithException<Object, Scoreboard.NameVisibility> setVisibility = (team, visibility) -> {};
|
||||
private BiConsumerWithException<Object, Scoreboard.CollisionRule> setCollision = (team, collision) -> {};
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all required NMS classes, fields and methods.
|
||||
* If anything fails, exception is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If anything fails
|
||||
*/
|
||||
public TeamPacketData() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Class<?> Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
Class<?> scoreboardTeam = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
|
||||
Class<?> enumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat", "EnumChatFormat");
|
||||
TeamPacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardTeam", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardTeam", // Bukkit 1.7 - 1.16.5
|
||||
"Packet209SetScoreboardTeam" // 1.5 - 1.6.4
|
||||
);
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
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);
|
||||
chatFormats = (Enum<?>[]) enumChatFormatClass.getMethod("values").invoke(null);
|
||||
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 >= 7) {
|
||||
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
}
|
||||
if (minorVersion >= 8) loadVisibility(scoreboardTeam);
|
||||
if (minorVersion >= 9) loadCollision(scoreboardTeam);
|
||||
if (minorVersion >= MODERN_TEAM_DATA_VERSION) {
|
||||
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
|
||||
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+}
|
||||
Component
|
||||
);
|
||||
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+}
|
||||
Component
|
||||
);
|
||||
} 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 >= STATIC_CONSTRUCTOR_VERSION) {
|
||||
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
|
||||
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
|
||||
} else {
|
||||
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void loadVisibility(@NonNull Class<?> scoreboardTeam) {
|
||||
Class<?> enumNameTagVisibility = BukkitReflection.getClass(
|
||||
"world.scores.Team$Visibility", // Mojang mapped
|
||||
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.17+
|
||||
"ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.8.1 - 1.16.5
|
||||
"EnumNameTagVisibility" // Bukkit 1.8.0
|
||||
);
|
||||
Enum<?>[] nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
|
||||
Method setNameTagVisibility = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
|
||||
enumNameTagVisibility
|
||||
);
|
||||
setVisibility = (team, visibility) -> setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void loadCollision(@NonNull Class<?> scoreboardTeam) {
|
||||
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
|
||||
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
|
||||
Enum<?>[] collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
|
||||
Method setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumTeamPush);
|
||||
setCollision = (team, collision) -> setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates team register packet with specified parameters.
|
||||
*
|
||||
* @param team
|
||||
* Team to register
|
||||
* @param prefix
|
||||
* Team prefix for 1.12-
|
||||
* @param prefixComponent
|
||||
* Team prefix component for 1.13+
|
||||
* @param suffix
|
||||
* Team suffix for 1.12-
|
||||
* @param suffixComponent
|
||||
* Team suffix component for 1.13+
|
||||
* @param visibility
|
||||
* Nametag visibility rule
|
||||
* @param collision
|
||||
* Player collision rule
|
||||
* @param players
|
||||
* Team members
|
||||
* @param options
|
||||
* Team flags
|
||||
* @param color
|
||||
* Team color for 1.13+
|
||||
* @return Register team packet with specified parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object registerTeam(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
|
||||
@NonNull String suffix, @Nullable Object suffixComponent,
|
||||
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
|
||||
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
|
||||
updateTeamData(team, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options, color);
|
||||
((Collection<String>) ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
|
||||
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
|
||||
return TeamPacketConstructor_ofBoolean.invoke(null, team, true);
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team, TeamAction.CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates unregister team packet with given team.
|
||||
*
|
||||
* @param team
|
||||
* Team to unregister
|
||||
* @return Packet for unregistering team
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object unregisterTeam(@NonNull Object team) {
|
||||
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
|
||||
return TeamPacketConstructor_of.invoke(null, team);
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team, TeamAction.REMOVE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates team update packet with specified parameters.
|
||||
*
|
||||
* @param team
|
||||
* Team to update
|
||||
* @param prefix
|
||||
* Team prefix for 1.12-
|
||||
* @param prefixComponent
|
||||
* Team prefix component for 1.13+
|
||||
* @param suffix
|
||||
* Team suffix for 1.12-
|
||||
* @param suffixComponent
|
||||
* Team suffix component for 1.13+
|
||||
* @param visibility
|
||||
* Nametag visibility rule
|
||||
* @param collision
|
||||
* Player collision rule
|
||||
* @param options
|
||||
* Team flags
|
||||
* @param color
|
||||
* Team color for 1.13+
|
||||
* @return Update team packet with specified parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object updateTeam(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
|
||||
@NonNull String suffix, @Nullable Object suffixComponent,
|
||||
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
updateTeamData(team, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options, color);
|
||||
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
|
||||
return TeamPacketConstructor_ofBoolean.invoke(null, team, false);
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team, TeamAction.UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates team properties.
|
||||
*
|
||||
* @param team
|
||||
* Team to update
|
||||
* @param prefix
|
||||
* Team prefix for 1.12-
|
||||
* @param prefixComponent
|
||||
* Team prefix component for 1.13+
|
||||
* @param suffix
|
||||
* Team suffix for 1.12-
|
||||
* @param suffixComponent
|
||||
* Team suffix component for 1.13+
|
||||
* @param visibility
|
||||
* Nametag visibility rule
|
||||
* @param collision
|
||||
* Player collision rule
|
||||
* @param options
|
||||
* Team flags
|
||||
* @param color
|
||||
* Team color for 1.13+
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void updateTeamData(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
|
||||
@NonNull String suffix, @Nullable Object suffixComponent,
|
||||
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
|
||||
if (BukkitReflection.getMinorVersion() >= MODERN_TEAM_DATA_VERSION) {
|
||||
ScoreboardTeam_setPrefix.invoke(team, prefixComponent);
|
||||
ScoreboardTeam_setSuffix.invoke(team, suffixComponent);
|
||||
ScoreboardTeam_setColor.invoke(team, chatFormats[color.ordinal()]);
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix.invoke(team, prefix);
|
||||
ScoreboardTeam_setSuffix.invoke(team, suffix);
|
||||
}
|
||||
setVisibility.accept(team, visibility);
|
||||
setCollision.accept(team, collision);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a team with given name.
|
||||
*
|
||||
* @param name
|
||||
* Team name
|
||||
* @return Team with specified name
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return newScoreboardTeam.newInstance(emptyScoreboard, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if packet is team packet and removes all real players from team
|
||||
* if sent by other plugins and anti-override is fully active on a player.
|
||||
*
|
||||
* @param player
|
||||
* Player who received the packet
|
||||
* @param packet
|
||||
* Received packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
|
||||
if (!TeamPacketClass.isInstance(packet)) return;
|
||||
int action = TeamPacket_ACTION.getInt(packet);
|
||||
if (action == TeamAction.REMOVE || action == TeamAction.UPDATE) return;
|
||||
TeamPacket_PLAYERS.set(packet, player.getScoreboard().onTeamPacket(
|
||||
action, (String) TeamPacket_NAME.get(packet), (Collection<String>) TeamPacket_PLAYERS.get(packet)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler using the almighty Bukkit API.
|
||||
*/
|
||||
public class BukkitTabList extends TabListBase<String> {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public BukkitTabList(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable String displayName) {
|
||||
Player p = Bukkit.getPlayer(entry);
|
||||
if (p == null) return;
|
||||
p.setPlayerListName(displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable String displayName) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toComponent(@NonNull TabComponent component) {
|
||||
return player.getPlatform().toBukkitFormat(component, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.util.BiFunctionWithException;
|
||||
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 {
|
||||
|
||||
private final PacketSender packetSender = new PacketSender();
|
||||
private final BiFunctionWithException<Object, Object, Object> createPacket;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS content. Throws exception if something went wrong.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something went wrong
|
||||
*/
|
||||
public PacketHeaderFooter() throws ReflectiveOperationException {
|
||||
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
Class<?> HeaderFooterClass = BukkitReflection.getClass("network.protocol.game.ClientboundTabListPacket",
|
||||
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
|
||||
ComponentConverter.ensureAvailable();
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor(Component, Component);
|
||||
createPacket = newHeaderFooter::newInstance;
|
||||
} else {
|
||||
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor();
|
||||
Field HEADER = ReflectionUtils.getFields(HeaderFooterClass, Component).get(0);
|
||||
Field FOOTER = ReflectionUtils.getFields(HeaderFooterClass, Component).get(1);
|
||||
createPacket = (header, footer) -> {
|
||||
Object packet = newHeaderFooter.newInstance();
|
||||
HEADER.set(packet, header);
|
||||
FOOTER.set(packet, footer);
|
||||
return packet;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends header/footer to player.
|
||||
*
|
||||
* @param player
|
||||
* Player to send header/footer to.
|
||||
* @param header
|
||||
* Header to use.
|
||||
* @param footer
|
||||
* Footer to use.
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull Object header, @NotNull Object footer) {
|
||||
packetSender.sendPacket(player.getPlayer(), createPacket.apply(header, footer));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.19.3+ servers using packets.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class PacketTabList1193 extends PacketTabList18 {
|
||||
|
||||
/** Map of actions to prevent creating new EnumSet on each packet send */
|
||||
private static final Map<Action, EnumSet<?>> actionToEnumSet = new EnumMap<>(Action.class);
|
||||
|
||||
private static Enum actionAddPlayer;
|
||||
private static Enum actionUpdateDisplayName;
|
||||
private static Enum actionUpdateLatency;
|
||||
|
||||
private static Constructor<?> newRemovePacket;
|
||||
|
||||
private static Field PlayerInfoData_UUID;
|
||||
private static Field PlayerInfoData_GameMode;
|
||||
private static Field PlayerInfoData_Listed;
|
||||
private static Field PlayerInfoData_RemoteChatSession;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public PacketTabList1193(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load all required NMS classes, fields and methods.
|
||||
* If anything fails, throws an exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void loadNew() throws ReflectiveOperationException {
|
||||
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType", "world.level.EnumGamemode");
|
||||
ActionClass = (Class<Enum>) BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action", // Mojang
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$a" // Bukkit
|
||||
);
|
||||
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
Class<?> playerInfoDataClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry", // Mojang
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$b" // Bukkit
|
||||
);
|
||||
|
||||
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
|
||||
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
|
||||
|
||||
loadSharedContent(playerInfoDataClass, EnumGamemodeClass);
|
||||
|
||||
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
|
||||
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
|
||||
Class<?> RemoteChatSession$Data = BukkitReflection.getClass("network.chat.RemoteChatSession$Data", "network.chat.RemoteChatSession$a");
|
||||
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
|
||||
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
|
||||
newRemovePacket = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoRemovePacket").getConstructor(List.class);
|
||||
|
||||
actionAddPlayer = Enum.valueOf(ActionClass, Action.ADD_PLAYER.name());
|
||||
actionUpdateDisplayName = Enum.valueOf(ActionClass, Action.UPDATE_DISPLAY_NAME.name());
|
||||
actionUpdateLatency = Enum.valueOf(ActionClass, Action.UPDATE_LATENCY.name());
|
||||
Enum actionUpdateGameMode = Enum.valueOf(ActionClass, Action.UPDATE_GAME_MODE.name());
|
||||
|
||||
actionToEnumSet.put(Action.ADD_PLAYER, EnumSet.allOf(ActionClass));
|
||||
actionToEnumSet.put(Action.UPDATE_GAME_MODE, EnumSet.of(actionUpdateGameMode));
|
||||
actionToEnumSet.put(Action.UPDATE_DISPLAY_NAME, EnumSet.of(actionUpdateDisplayName));
|
||||
actionToEnumSet.put(Action.UPDATE_LATENCY, EnumSet.of(actionUpdateLatency));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
packetSender.sendPacket(player.getPlayer(), newRemovePacket.newInstance(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NonNull
|
||||
@Override
|
||||
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
int latency, int gameMode, @Nullable Object displayName) {
|
||||
Object packet = newPlayerInfo.newInstance(actionToEnumSet.get(action), Collections.emptyList());
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(
|
||||
id,
|
||||
action == Action.ADD_PLAYER ? createProfile(id, name, skin) : null,
|
||||
true,
|
||||
latency,
|
||||
gameModes[gameMode],
|
||||
displayName,
|
||||
null
|
||||
)));
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (!(PlayerInfoClass.isInstance(packet))) return;
|
||||
EnumSet<?> actions = (EnumSet<?>) ACTION.get(packet);
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (Object nmsData : (List<?>) PLAYERS.get(packet)) {
|
||||
boolean rewriteEntry = false;
|
||||
UUID id = (UUID) PlayerInfoData_UUID.get(nmsData);
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
Object displayName = PlayerInfoData_DisplayName.get(nmsData);
|
||||
int latency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
if (actions.contains(actionUpdateDisplayName)) {
|
||||
Object expectedName = getExpectedDisplayName(id);
|
||||
if (expectedName != null && expectedName != displayName) {
|
||||
displayName = expectedName;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(actionUpdateLatency)) {
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, latency);
|
||||
if (newLatency != latency) {
|
||||
latency = newLatency;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(actionAddPlayer)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
|
||||
updatedList.add(rewriteEntry ? newPlayerInfoData.newInstance(
|
||||
id,
|
||||
profile,
|
||||
PlayerInfoData_Listed.getBoolean(nmsData),
|
||||
latency,
|
||||
PlayerInfoData_GameMode.get(nmsData),
|
||||
displayName,
|
||||
PlayerInfoData_RemoteChatSession.get(nmsData)) : nmsData);
|
||||
}
|
||||
if (rewritePacket) PLAYERS.set(packet, updatedList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.TriFunctionWithException;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.7- servers using packets.
|
||||
*/
|
||||
public class PacketTabList17 extends TabListBase<String> {
|
||||
|
||||
private static TriFunctionWithException<String, Boolean, Integer, Object> newPacket;
|
||||
private static PacketSender packetSender;
|
||||
|
||||
/** Because entries are identified by names and not uuids on 1.7- */
|
||||
@NonNull
|
||||
private final Map<UUID, String> userNames = new HashMap<>();
|
||||
|
||||
@NonNull
|
||||
private final Map<UUID, String> displayNames = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public PacketTabList17(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load all required NMS classes, fields and methods.
|
||||
* If anything fails, throws an exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
Class<?> PlayerInfoClass = BukkitReflection.getClass("PacketPlayOutPlayerInfo", "Packet201PlayerInfo");
|
||||
try {
|
||||
Constructor<?> newPlayerInfo = PlayerInfoClass.getConstructor(String.class, boolean.class, int.class);
|
||||
newPacket = newPlayerInfo::newInstance;
|
||||
} catch (NoSuchMethodException e) {
|
||||
// 1.7.10 spigot with protocol hack
|
||||
Constructor<?> newPlayerInfo = PlayerInfoClass.getConstructor();
|
||||
Field USERNAME = ReflectionUtils.getField(PlayerInfoClass, "username");
|
||||
Field ACTION = ReflectionUtils.getField(PlayerInfoClass, "action");
|
||||
Field PING = ReflectionUtils.getField(PlayerInfoClass, "ping");
|
||||
newPacket = (name, addOrUpdate, latency) -> {
|
||||
Object packet = newPlayerInfo.newInstance();
|
||||
USERNAME.set(packet, name);
|
||||
ACTION.set(packet, addOrUpdate ? 3 : 4);
|
||||
PING.set(packet, latency);
|
||||
return packet;
|
||||
};
|
||||
}
|
||||
packetSender = new PacketSender();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player.getPlayer(), newPacket.apply(displayNames.get(entry), false, 0));
|
||||
userNames.remove(entry);
|
||||
displayNames.remove(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable String displayName) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player.getPlayer(), newPacket.apply(displayNames.get(entry), false, 0));
|
||||
addEntry0(entry, userNames.get(entry), null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player.getPlayer(), newPacket.apply(displayNames.get(entry), true, latency));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
// Added in 1.8
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable String displayName) {
|
||||
String display = displayName == null ? name : displayName;
|
||||
packetSender.sendPacket(player.getPlayer(), newPacket.apply(display, true, latency));
|
||||
userNames.put(id, name);
|
||||
displayNames.put(id, display);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toComponent(@NonNull TabComponent component) {
|
||||
String name = component.toLegacyText();
|
||||
if (name.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7) name = name.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.8 - 1.19.2 servers using packets.
|
||||
*/
|
||||
@Setter
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class PacketTabList18 extends TabListBase<Object> {
|
||||
|
||||
protected static Class<?> PlayerInfoClass;
|
||||
protected static Constructor<?> newPlayerInfo;
|
||||
protected static Field ACTION;
|
||||
protected static Field PLAYERS;
|
||||
protected static Class<Enum> ActionClass;
|
||||
|
||||
protected static Constructor<?> newPlayerInfoData;
|
||||
protected static Field PlayerInfoData_Profile;
|
||||
protected static Field PlayerInfoData_Latency;
|
||||
protected static Field PlayerInfoData_DisplayName;
|
||||
|
||||
protected static Object[] gameModes;
|
||||
|
||||
protected static PacketSender packetSender;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public PacketTabList18(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load all required NMS classes, fields and methods.
|
||||
* If anything fails, throws an exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType",
|
||||
"world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode");
|
||||
ActionClass = (Class<Enum>) BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket$Action", // Mojang 1.17 - 1.19.2
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.17 - 1.19.2
|
||||
"PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.8.1 - 1.16.5
|
||||
"EnumPlayerInfoAction" // Bukkit 1.8.0
|
||||
);
|
||||
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket",
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket",
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo");
|
||||
Class<?> playerInfoDataClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", // Mojang 1.17 - 1.19.2
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.17 - 1.19.2
|
||||
"PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.8.1 - 1.16.5
|
||||
"PlayerInfoData" // Bukkit 1.8.0
|
||||
);
|
||||
|
||||
Class<?> classType = BukkitReflection.getMinorVersion() >= 17 ? Collection.class : Iterable.class;
|
||||
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, classType);
|
||||
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
|
||||
|
||||
loadSharedContent(playerInfoDataClass, EnumGamemodeClass);
|
||||
}
|
||||
|
||||
protected static void loadSharedContent(Class<?> infoData, Class<Enum> gameMode) throws ReflectiveOperationException {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
newPlayerInfoData = infoData.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
|
||||
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
|
||||
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(infoData, GameProfile.class);
|
||||
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(infoData, int.class);
|
||||
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(infoData, IChatBaseComponent);
|
||||
gameModes = new Object[] {
|
||||
Enum.valueOf(gameMode, "SURVIVAL"),
|
||||
Enum.valueOf(gameMode, "CREATIVE"),
|
||||
Enum.valueOf(gameMode, "ADVENTURE"),
|
||||
Enum.valueOf(gameMode, "SPECTATOR")
|
||||
};
|
||||
packetSender = new PacketSender();
|
||||
ComponentConverter.ensureAvailable();
|
||||
try {
|
||||
skinData = new SkinData();
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "getting player's game profile", null,
|
||||
"Player skins not working in layout feature");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
packetSender.sendPacket(player.getPlayer(),
|
||||
createPacket(Action.REMOVE_PLAYER, entry, "", null, 0, 0, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable Object displayName) {
|
||||
packetSender.sendPacket(player.getPlayer(),
|
||||
createPacket(Action.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
packetSender.sendPacket(player.getPlayer(),
|
||||
createPacket(Action.UPDATE_LATENCY, entry, "", null, latency, 0, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
packetSender.sendPacket(player.getPlayer(),
|
||||
createPacket(Action.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable Object displayName) {
|
||||
packetSender.sendPacket(player.getPlayer(),
|
||||
createPacket(Action.ADD_PLAYER, id, name, skin, latency, gameMode, displayName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates packet from given parameters.
|
||||
*
|
||||
* @param action
|
||||
* Packet action
|
||||
* @param id
|
||||
* Entry UUID
|
||||
* @param name
|
||||
* Entry name
|
||||
* @param skin
|
||||
* Entry skin
|
||||
* @param latency
|
||||
* Entry latency
|
||||
* @param gameMode
|
||||
* Entry game mode
|
||||
* @param displayName
|
||||
* Entry display name
|
||||
* @return Packet from given parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
@NonNull
|
||||
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
int latency, int gameMode, @Nullable Object displayName) {
|
||||
Object packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Collections.emptyList());
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
|
||||
parameters.add(packet);
|
||||
}
|
||||
parameters.add(createProfile(id, name, skin));
|
||||
parameters.add(latency);
|
||||
parameters.add(gameModes[gameMode]);
|
||||
parameters.add(displayName);
|
||||
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(parameters.toArray())));
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NonNull
|
||||
public GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (!(PlayerInfoClass.isInstance(packet))) return;
|
||||
String action = ACTION.get(packet).toString();
|
||||
for (Object nmsData : (List<?>) PLAYERS.get(packet)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
if (action.equals(Action.UPDATE_DISPLAY_NAME.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
Object expectedName = getExpectedDisplayName(id);
|
||||
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName);
|
||||
}
|
||||
if (action.equals(Action.UPDATE_LATENCY.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
int latency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, PlayerInfoData_Latency.getInt(nmsData));
|
||||
PlayerInfoData_Latency.set(nmsData, latency);
|
||||
}
|
||||
if (action.equals(Action.ADD_PLAYER.name())) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Class for getting skin of players.
|
||||
*/
|
||||
public class SkinData {
|
||||
|
||||
private final Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
|
||||
|
||||
// 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
|
||||
private final Method getProfile = ReflectionUtils.getMethods(BukkitReflection.getClass(
|
||||
"world.entity.player.Player", "world.entity.player.EntityHuman", "EntityHuman"), GameProfile.class).get(0);
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads NMS fields. If it fails, throws an Exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public SkinData() throws ReflectiveOperationException {}
|
||||
|
||||
/**
|
||||
* Returns player's skin. If server is in offline mode, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get skin of
|
||||
* @return Player's skin or {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
public TabList.Skin getSkin(@NotNull Player player) {
|
||||
Collection<Property> col = ((GameProfile) getProfile.invoke(getHandle.invoke(player))).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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
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.platform.TabList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Base TabList class for all implementations.
|
||||
*
|
||||
* @param <C>
|
||||
* Component class
|
||||
*/
|
||||
public abstract class TabListBase<C> extends TabList<BukkitTabPlayer, C> {
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
private static Function<BukkitTabPlayer, TabListBase<?>> instance;
|
||||
|
||||
@Nullable
|
||||
protected static SkinData skinData;
|
||||
|
||||
@Nullable
|
||||
private static PacketHeaderFooter headerFooter;
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
protected TabListBase(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the best available instance for current server software.
|
||||
*/
|
||||
public static void findInstance() {
|
||||
try {
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
PacketTabList1193.loadNew();
|
||||
instance = PacketTabList1193::new;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
PacketTabList18.load();
|
||||
instance = PacketTabList18::new;
|
||||
} else {
|
||||
PacketTabList17.load();
|
||||
instance = PacketTabList17::new;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
|
||||
"Layout feature will not work",
|
||||
"Prevent-spectator-effect feature will not work",
|
||||
"Ping spoof feature will not work",
|
||||
"Tablist formatting missing anti-override",
|
||||
"Tablist formatting not supporting relational placeholders");
|
||||
instance = BukkitTabList::new;
|
||||
}
|
||||
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
headerFooter = new PacketHeaderFooter();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "sending header/footer", null,
|
||||
"Header/footer will not work");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull Object header, @NonNull Object footer) {
|
||||
if (headerFooter != null) headerFooter.set(player, header, footer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player's skin. If NMS fields did not load or server is in
|
||||
* offline mode, returns {@code null}.
|
||||
*
|
||||
* @return Player's skin or {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
if (skinData == null) return null;
|
||||
return skinData.getSkin(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,11 @@ load: POSTWORLD
|
||||
author: @author@
|
||||
website: "@website@"
|
||||
depend: []
|
||||
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate]
|
||||
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate, Vault]
|
||||
loadbefore: []
|
||||
prefix: TAB
|
||||
libraries: []
|
||||
folia-supported: false
|
||||
folia-supported: true
|
||||
commands:
|
||||
tab:
|
||||
description: Plugin's main command
|
||||
@@ -49,9 +49,6 @@ permissions:
|
||||
tab.change.*:
|
||||
description: Allows player to change property using command
|
||||
default: op
|
||||
tab.width:
|
||||
description: Allows player to run /tab width
|
||||
default: op
|
||||
tab.announce.bar:
|
||||
description: Allows player to run /tab announce bar
|
||||
default: op
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bungeecord:3.0.1")
|
||||
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.1-SNAPSHOT")
|
||||
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.2-SNAPSHOT")
|
||||
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
|
||||
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -18,11 +18,15 @@ import java.util.UUID;
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeBossBar implements BossBar {
|
||||
|
||||
private final @NotNull BungeeTabPlayer player;
|
||||
|
||||
@NotNull
|
||||
private final BungeeTabPlayer player;
|
||||
|
||||
/** Both are included in the same action, need to remember and write them both */
|
||||
@NotNull
|
||||
private final Map<UUID, Integer> colors = new HashMap<>();
|
||||
|
||||
@NotNull
|
||||
private final Map<UUID, Integer> styles = new HashMap<>();
|
||||
|
||||
@Override
|
||||
@@ -31,10 +35,10 @@ public class BungeeBossBar implements BossBar {
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 0);
|
||||
packet.setHealth(progress);
|
||||
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
|
||||
packet.setTitle(TabComponent.optimized(title).convert(player.getVersion()));
|
||||
packet.setColor(color.ordinal());
|
||||
packet.setDivision(style.ordinal());
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
player.sendPacket(packet);
|
||||
|
||||
colors.put(id, color.ordinal());
|
||||
styles.put(id, style.ordinal());
|
||||
@@ -45,8 +49,8 @@ public class BungeeBossBar implements BossBar {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 3);
|
||||
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
packet.setTitle(TabComponent.optimized(title).convert(player.getVersion()));
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,7 +59,7 @@ public class BungeeBossBar implements BossBar {
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 2);
|
||||
packet.setHealth(progress);
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,7 +69,7 @@ public class BungeeBossBar implements BossBar {
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
|
||||
packet.setDivision(style.ordinal());
|
||||
packet.setColor(colors.get(id));
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
player.sendPacket(packet);
|
||||
|
||||
styles.put(id, style.ordinal());
|
||||
}
|
||||
@@ -77,7 +81,7 @@ public class BungeeBossBar implements BossBar {
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
|
||||
packet.setDivision(styles.get(id));
|
||||
packet.setColor(color.ordinal());
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
player.sendPacket(packet);
|
||||
|
||||
colors.put(id, color.ordinal());
|
||||
}
|
||||
@@ -86,7 +90,7 @@ public class BungeeBossBar implements BossBar {
|
||||
public void remove(@NotNull UUID id) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
player.getPlayer().unsafe().sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
|
||||
player.sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
|
||||
|
||||
colors.remove(id);
|
||||
styles.remove(id);
|
||||
|
||||
+68
-10
@@ -1,5 +1,7 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -10,40 +12,96 @@ import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerSwitchEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The core for BungeeCord forwarding events into all enabled features
|
||||
*/
|
||||
public class BungeeEventListener extends EventListener<ProxiedPlayer> implements Listener {
|
||||
public class BungeeEventListener implements EventListener<ProxiedPlayer>, Listener {
|
||||
|
||||
/**
|
||||
* Listens to player disconnecting from the server.
|
||||
*
|
||||
* @param e
|
||||
* Disconnect event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onQuit(PlayerDisconnectEvent e) {
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to player connecting to a backend server. This handles
|
||||
* both initial connections and server switch.
|
||||
*
|
||||
* @param e
|
||||
* Server switch event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onSwitch(ServerSwitchEvent e) {
|
||||
serverChange(e.getPlayer(), e.getPlayer().getUniqueId(), e.getPlayer().getServer().getInfo().getName());
|
||||
TAB tab = TAB.getInstance();
|
||||
if (tab.isPluginDisabled()) return;
|
||||
|
||||
// Avoid 1.20.3 client crash on scoreboard packets, do it sync to prevent packet being sent after event, but before processing
|
||||
TabPlayer p = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (p != null && p.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
|
||||
p.getScoreboard().freeze();
|
||||
}
|
||||
|
||||
tab.getCPUManager().runTask(() -> {
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player == null) {
|
||||
player = createPlayer(e.getPlayer());
|
||||
|
||||
// Things will get cleared immediately after, so no point in sending it, also someone said it fixed some warn from Geyser
|
||||
// Sending these packets before login packet will also crash the client on 1.20.3
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
|
||||
player.getScoreboard().freeze();
|
||||
}
|
||||
|
||||
tab.getFeatureManager().onJoin(player);
|
||||
} else {
|
||||
tab.getFeatureManager().onServerChange(player.getUniqueId(), e.getPlayer().getServer().getInfo().getName());
|
||||
if (player.getVersion().getNetworkId() < ProtocolVersion.V1_20_2.getNetworkId()) {
|
||||
// For versions below 1.20.2 the tablist is already clean when this event is called
|
||||
// For 1.20.2+ this event is called before, so we listen to Login packet instead
|
||||
tab.getFeatureManager().onTabListClear(player);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to command execute event to potentially cancel it.
|
||||
*
|
||||
* @param e
|
||||
* Command execute event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onChat(ChatEvent e) {
|
||||
public void onCommand(ChatEvent e) {
|
||||
if (e.isCommand() && command(((ProxiedPlayer)e.getSender()).getUniqueId(), e.getMessage())) {
|
||||
e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to plugin messages.
|
||||
*
|
||||
* @param e
|
||||
* Plugin message event
|
||||
*/
|
||||
@EventHandler
|
||||
public void on(PluginMessageEvent event) {
|
||||
if (!event.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
|
||||
if (event.getReceiver() instanceof ProxiedPlayer) {
|
||||
event.setCancelled(true);
|
||||
pluginMessage(((ProxiedPlayer) event.getReceiver()).getUniqueId(), event.getData());
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (!e.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
|
||||
if (e.getReceiver() instanceof ProxiedPlayer) {
|
||||
e.setCancelled(true);
|
||||
pluginMessage(((ProxiedPlayer) e.getReceiver()).getUniqueId(), e.getData());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabPlayer createPlayer(ProxiedPlayer player) {
|
||||
return new BungeeTabPlayer(player);
|
||||
@NotNull
|
||||
public TabPlayer createPlayer(@NotNull ProxiedPlayer player) {
|
||||
return new BungeeTabPlayer((BungeePlatform) TAB.getInstance().getPlatform(), player);
|
||||
}
|
||||
}
|
||||
+70
-120
@@ -1,32 +1,29 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.features.redis.feature.RedisTeams;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.redis.RedisPlayer;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.packet.*;
|
||||
import net.md_5.bungee.protocol.Protocol;
|
||||
import net.md_5.bungee.protocol.packet.Login;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
|
||||
import net.md_5.bungee.protocol.packet.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -38,126 +35,59 @@ import java.util.function.Supplier;
|
||||
@SuppressWarnings("unchecked")
|
||||
public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
private static @Nullable Field wrapperField;
|
||||
/** Inaccessible bungee internals */
|
||||
@Nullable
|
||||
private static Object directionData;
|
||||
|
||||
@Nullable
|
||||
private static Method getId;
|
||||
|
||||
static {
|
||||
try {
|
||||
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
|
||||
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
|
||||
getId = ReflectionUtils.setAccessible(Protocol.DirectionData.class.getDeclaredMethod("getId", Class.class, int.class));
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/** Packets used by the plugin that must be deserialized and BungeeCord does not do it automatically */
|
||||
@NotNull
|
||||
private final Class<? extends DefinedPacket>[] extraPacketClasses = new Class[]{Team.class, ScoreboardDisplay.class, ScoreboardObjective.class};
|
||||
|
||||
@NotNull
|
||||
private final Supplier<DefinedPacket>[] extraPacketSuppliers = new Supplier[]{Team::new, ScoreboardDisplay::new, ScoreboardObjective::new};
|
||||
|
||||
@Override
|
||||
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
|
||||
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : TabChannelDuplexHandler::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Channel getChannel(@NotNull TabPlayer player) {
|
||||
if (wrapperField == null) return null;
|
||||
final BungeeTabPlayer bungee = (BungeeTabPlayer) player;
|
||||
try {
|
||||
return ((ChannelWrapper) wrapperField.get(bungee.getPlayer().getPendingConnection())).getHandle();
|
||||
} catch (final IllegalAccessException exception) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
|
||||
((ScoreboardDisplay) packet).getPosition(), ((ScoreboardDisplay) packet).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
((ScoreboardObjective) packet).getAction(), ((ScoreboardObjective) packet).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayObjective(@NotNull Object packet) {
|
||||
return packet instanceof ScoreboardDisplay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObjective(@NotNull Object packet) {
|
||||
return packet instanceof ScoreboardObjective;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeam(@NotNull Object packet) {
|
||||
return packet instanceof Team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(@NotNull Object packet) {
|
||||
return packet instanceof PlayerListItem || packet instanceof PlayerListItemUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyPlayers(@NotNull Object team) {
|
||||
if (TAB.getInstance().getNameTagManager() == null) return;
|
||||
Team packet = (Team) team;
|
||||
if (packet.getMode() == 1 || packet.getMode() == 2 || packet.getMode() == 4) return;
|
||||
Collection<String> col = Lists.newArrayList(packet.getPlayers());
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
String expectedTeam = sorting.getShortTeamName(p);
|
||||
if (expectedTeam != null && col.contains(p.getName()) &&
|
||||
!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !packet.getName().equals(expectedTeam)) {
|
||||
logTeamOverride(packet.getName(), p.getName(), expectedTeam);
|
||||
col.remove(p.getName());
|
||||
}
|
||||
}
|
||||
RedisSupport redis = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
|
||||
if (redis != null) {
|
||||
RedisTeams teams = redis.getRedisTeams();
|
||||
if (teams != null) {
|
||||
for (RedisPlayer p : redis.getRedisPlayers().values()) {
|
||||
if (col.contains(p.getName()) && !packet.getName().equals(teams.getTeamNames().get(p))) {
|
||||
logTeamOverride(packet.getName(), p.getName(), teams.getTeamNames().get(p));
|
||||
col.remove(p.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
packet.setPlayers(col.toArray(new String[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
if (packet instanceof PlayerListItemUpdate) {
|
||||
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
|
||||
for (PlayerListItem.Item item : update.getItems()) {
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
|
||||
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PlayerListItem listItem = (PlayerListItem) packet;
|
||||
for (PlayerListItem.Item item : listItem.getItems()) {
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
|
||||
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Whether ByteBuf deserialization should be enabled or not */
|
||||
protected boolean byteBufDeserialization;
|
||||
|
||||
/**
|
||||
* Constructs new instance of the feature
|
||||
*/
|
||||
public BungeePipelineInjector() {
|
||||
super("inbound-boss");
|
||||
boolean antiOverrideTeams = config().getBoolean("scoreboard-teams.enabled", true) &&
|
||||
config().getBoolean("scoreboard-teams.anti-override", true);
|
||||
boolean respectOtherScoreboardPlugins = config().getBoolean("scoreboard.enabled", false) &&
|
||||
config().getBoolean("scoreboard.respect-other-plugins", true);
|
||||
byteBufDeserialization = antiOverrideTeams || respectOtherScoreboardPlugins;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
|
||||
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : TabChannelDuplexHandler::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return ((UserConnection) ((BungeeTabPlayer) player).getPlayer()).getCh().getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogin(@NotNull Object packet) {
|
||||
return packet instanceof Login;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,7 +95,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
* deserialized manually are used. If they are disabled, deserialization is
|
||||
* disabled for better performance.
|
||||
*/
|
||||
public class DeserializableBungeeChannelDuplexHandler extends TabChannelDuplexHandler {
|
||||
private class DeserializableBungeeChannelDuplexHandler extends TabChannelDuplexHandler {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player
|
||||
@@ -173,12 +103,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
* @param player
|
||||
* player to inject
|
||||
*/
|
||||
public DeserializableBungeeChannelDuplexHandler(TabPlayer player) {
|
||||
private DeserializableBungeeChannelDuplexHandler(@NotNull TabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
|
||||
public void write(@NotNull ChannelHandlerContext context, @NotNull Object packet, @NotNull ChannelPromise channelPromise) {
|
||||
long time = System.nanoTime();
|
||||
Object modifiedPacket = packet instanceof ByteBuf ? deserialize((ByteBuf) packet) : packet;
|
||||
TAB.getInstance().getCPUManager().addTime(TabConstants.Feature.PACKET_DESERIALIZING, TabConstants.CpuUsageCategory.BYTE_BUF, System.nanoTime()-time);
|
||||
@@ -193,12 +123,17 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
* byte buf to deserialize
|
||||
* @return deserialized packet or input byte buf if packet is not tracked
|
||||
*/
|
||||
private @NotNull Object deserialize(@NotNull ByteBuf buf) {
|
||||
@NotNull
|
||||
private Object deserialize(@NotNull ByteBuf buf) {
|
||||
int marker = buf.readerIndex();
|
||||
try {
|
||||
int packetId = buf.readByte();
|
||||
for (int i=0; i<extraPacketClasses.length; i++) {
|
||||
if (packetId == ((BungeeTabPlayer)player).getPacketId(extraPacketClasses[i])) {
|
||||
ChannelWrapper ch = ((UserConnection) ((BungeeTabPlayer) player).getPlayer()).getCh();
|
||||
if (!ch.getEncodeProtocol().TO_CLIENT.hasPacket(extraPacketClasses[i], ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion())) {
|
||||
continue;
|
||||
}
|
||||
if (packetId == getPacketId(((BungeeTabPlayer)player).getPlayer().getPendingConnection().getVersion(), extraPacketClasses[i])) {
|
||||
DefinedPacket packet = extraPacketSuppliers[i].get();
|
||||
packet.read(buf, null, ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion());
|
||||
buf.release();
|
||||
@@ -211,5 +146,20 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
buf.readerIndex(marker);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns packet ID of specified packet on the protocol version
|
||||
*
|
||||
* @param protocolVersion
|
||||
* Protocol version to get packet id for
|
||||
* @param clazz
|
||||
* packet class
|
||||
* @return packet ID
|
||||
*/
|
||||
@SneakyThrows
|
||||
private int getPacketId(int protocolVersion, @NotNull Class<? extends DefinedPacket> clazz) {
|
||||
if (getId == null) return -1;
|
||||
return (int) getId.invoke(directionData, clazz, protocolVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,160 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bungeecord.features.BungeeRedisSupport;
|
||||
import me.neznamy.tab.platforms.bungeecord.hook.BungeePremiumVanishHook;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.*;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.hook.ViaVersionHook;
|
||||
import me.neznamy.tab.shared.proxy.ProxyPlatform;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ProxyConfig;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* BungeeCord implementation of Platform
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
@NotNull private final BungeeTAB plugin;
|
||||
@NotNull
|
||||
private final BungeeTAB plugin;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin instance.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance
|
||||
*/
|
||||
public BungeePlatform(@NotNull BungeeTAB plugin) {
|
||||
this.plugin = plugin;
|
||||
if (ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null) {
|
||||
PremiumVanishHook.setInstance(new BungeePremiumVanishHook(this));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
ViaVersionHook.getInstance().printProxyWarn();
|
||||
try {
|
||||
ProxyConfig config = ProxyServer.getInstance().getConfig();
|
||||
if ((boolean) config.getClass().getMethod("isDisableTabListRewrite").invoke(config)) {
|
||||
logWarn(new SimpleComponent("Waterfall's \"disable_tab_list_rewrite: true\" option may cause " +
|
||||
"the plugin to not work correctly. Disable it to avoid issues."));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Not waterfall
|
||||
}
|
||||
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||
TAB.getInstance().addPlayer(new BungeeTabPlayer(this, p));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable RedisSupport getRedisSupport() {
|
||||
@Nullable
|
||||
public RedisSupport getRedisSupport() {
|
||||
if (ReflectionUtils.classExists("com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI") &&
|
||||
RedisBungeeAPI.getRedisBungeeApi() != null) {
|
||||
return new RedisBungeeSupport(plugin);
|
||||
return new BungeeRedisSupport(plugin);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
plugin.getLogger().info(message.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
plugin.getLogger().warning(EnumChatFormat.RED + message.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[BungeeCord] " + plugin.getProxy().getName() + " - " + plugin.getProxy().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||
TAB.getInstance().addPlayer(new BungeeTabPlayer(p));
|
||||
}
|
||||
public void registerListener() {
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(plugin, new BungeeEventListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PipelineInjector createPipelineInjector() {
|
||||
public void registerCommand() {
|
||||
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new BungeeTabCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
new Metrics(plugin, TabConstants.BSTATS_PLUGIN_ID_BUNGEE).addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED,
|
||||
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return new BungeePipelineInjector();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerChannel() {
|
||||
ProxyServer.getInstance().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseComponent convertComponent(@NotNull TabComponent component, boolean modern) {
|
||||
if (component instanceof SimpleComponent) return new TextComponent(component.toLegacyText());
|
||||
StructuredComponent iComponent = (StructuredComponent) component;
|
||||
TextComponent textComponent = new TextComponent(iComponent.getText());
|
||||
ChatModifier modifier = iComponent.getModifier();
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
textComponent.setColor(ChatColor.of("#" + modifier.getColor().getHexCode()));
|
||||
} else {
|
||||
textComponent.setColor(ChatColor.of(modifier.getColor().getLegacyColor().name()));
|
||||
}
|
||||
}
|
||||
|
||||
if (modifier.isBold()) textComponent.setBold(true);
|
||||
if (modifier.isItalic()) textComponent.setItalic(true);
|
||||
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
|
||||
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
|
||||
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
|
||||
|
||||
textComponent.setFont(modifier.getFont());
|
||||
|
||||
if (!iComponent.getExtra().isEmpty()) {
|
||||
List<BaseComponent> list = new ArrayList<>();
|
||||
for (StructuredComponent extra : iComponent.getExtra()) {
|
||||
list.add(convertComponent(extra, modern));
|
||||
}
|
||||
textComponent.setExtra(list);
|
||||
}
|
||||
|
||||
return textComponent;
|
||||
}
|
||||
}
|
||||
+119
-56
@@ -1,14 +1,17 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.NonNull;
|
||||
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.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardScore;
|
||||
import net.md_5.bungee.protocol.packet.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.protocol.Either;
|
||||
import net.md_5.bungee.protocol.NumberFormat;
|
||||
import net.md_5.bungee.protocol.packet.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -17,86 +20,146 @@ import java.util.Collection;
|
||||
* any Scoreboard API and the scoreboard class it has is just a
|
||||
* downstream tracker, we need to use packets.
|
||||
*/
|
||||
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer, BaseComponent> {
|
||||
|
||||
public BungeeScoreboard(BungeeTabPlayer player) {
|
||||
/** Version with a minor team recode */
|
||||
private final int TEAM_REWORK_VERSION = 13;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public BungeeScoreboard(@NonNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
player.sendPacket(new ScoreboardDisplay((byte)slot.ordinal(), objective));
|
||||
public void setDisplaySlot0(int slot, @NonNull String objective) {
|
||||
player.sendPacket(new ScoreboardDisplay(slot, objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 0));
|
||||
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable BaseComponent numberFormat) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
objectiveName,
|
||||
either(title),
|
||||
ScoreboardObjective.HealthDisplay.values()[display],
|
||||
(byte) ObjectiveAction.REGISTER,
|
||||
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
player.sendPacket(new ScoreboardObjective(objectiveName, "", null, (byte) 1)); // Empty string to prevent kick on 1.7
|
||||
public void unregisterObjective0(@NonNull String objectiveName) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
objectiveName,
|
||||
either(""), // Empty value instead of null to prevent NPE kick on 1.7
|
||||
null,
|
||||
(byte) ObjectiveAction.UNREGISTER,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 2));
|
||||
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable BaseComponent numberFormat) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
objectiveName,
|
||||
either(title),
|
||||
ScoreboardObjective.HealthDisplay.values()[display],
|
||||
(byte) ObjectiveAction.UPDATE,
|
||||
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
|
||||
int color = 0;
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
|
||||
}
|
||||
player.sendPacket(new Team(name, (byte) 0, jsonOrRaw(name, player.getVersion()),
|
||||
jsonOrRaw(prefix, player.getVersion()), jsonOrRaw(suffix, player.getVersion()),
|
||||
visibility.toString(), collision.toString(), color, (byte)options, players.toArray(new String[0])));
|
||||
public void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
|
||||
player.sendPacket(new Team(
|
||||
name,
|
||||
(byte) TeamAction.CREATE,
|
||||
either(name),
|
||||
either(prefix),
|
||||
either(suffix),
|
||||
visibility.toString(),
|
||||
collision.toString(),
|
||||
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? color.ordinal() : 0,
|
||||
(byte)options,
|
||||
players.toArray(new String[0])
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
public void unregisterTeam0(@NonNull String name) {
|
||||
player.sendPacket(new Team(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
int color = 0;
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
|
||||
}
|
||||
player.sendPacket(new Team(name, (byte) 2, jsonOrRaw(name, player.getVersion()),
|
||||
jsonOrRaw(prefix, player.getVersion()), jsonOrRaw(suffix, player.getVersion()),
|
||||
visibility.toString(), collision.toString(), color, (byte)options, null));
|
||||
public void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
player.sendPacket(new Team(
|
||||
name,
|
||||
(byte) TeamAction.UPDATE,
|
||||
either(name),
|
||||
either(prefix),
|
||||
either(suffix),
|
||||
visibility.toString(),
|
||||
collision.toString(),
|
||||
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? color.ordinal() : 0,
|
||||
(byte)options,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
|
||||
* it as a serialized component, which BungeeCord uses.
|
||||
* <p>
|
||||
* If {@code clientVersion} is < 1.12, the text is returned
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @param clientVersion
|
||||
* Version of player to convert text for
|
||||
* @return serialized component for 1.13+ clients, cut string for 1.12-
|
||||
*/
|
||||
private String jsonOrRaw(@NotNull String text, @NotNull ProtocolVersion clientVersion) {
|
||||
if (clientVersion.getMinorVersion() >= 13) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
|
||||
@Override
|
||||
public void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
|
||||
@Nullable BaseComponent displayName, @Nullable BaseComponent numberFormat) {
|
||||
player.sendPacket(new ScoreboardScore(
|
||||
scoreHolder,
|
||||
(byte) ScoreAction.CHANGE,
|
||||
objective,
|
||||
score,
|
||||
displayName,
|
||||
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
|
||||
player.sendPacket(new ScoreboardScoreReset(scoreHolder, objective));
|
||||
} else {
|
||||
return text;
|
||||
player.sendPacket(new ScoreboardScore(scoreHolder, (byte) ScoreAction.REMOVE, objective, 0, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
private Either<String, BaseComponent> either(@NonNull String text) {
|
||||
if (player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION) {
|
||||
return Either.right(TabComponent.optimized(text).convert(player.getVersion()));
|
||||
} else {
|
||||
return Either.left(text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ScoreboardDisplay) {
|
||||
ScoreboardDisplay display = (ScoreboardDisplay) packet;
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getPosition(), display.getName());
|
||||
}
|
||||
if (packet instanceof ScoreboardObjective) {
|
||||
ScoreboardObjective objective = (ScoreboardObjective) packet;
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getAction(), objective.getName());
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof Team) {
|
||||
Team team = (Team) packet;
|
||||
if (team.getMode() == TeamAction.REMOVE || team.getMode() == TeamAction.UPDATE) return;
|
||||
team.setPlayers(onTeamPacket(team.getMode(), team.getName(), Lists.newArrayList(team.getPlayers())).toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,23 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
|
||||
/**
|
||||
* Main class for BungeeCord platform
|
||||
* Main class for BungeeCord.
|
||||
*/
|
||||
public class BungeeTAB extends Plugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
|
||||
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 and up (or an equivalent fork) to work."));
|
||||
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.ScoreboardScoreReset")) {
|
||||
getLogger().warning(EnumChatFormat.RED + "The plugin requires BungeeCord build #1774 " +
|
||||
"(released on November 25th, 2023) and up (or an equivalent fork) to work.");
|
||||
return;
|
||||
}
|
||||
getProxy().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
|
||||
getProxy().getPluginManager().registerListener(this, new BungeeEventListener());
|
||||
getProxy().getPluginManager().registerCommand(this, new BungeeTabCommand());
|
||||
TAB.setInstance(new TAB(new BungeePlatform(this), ProtocolVersion.PROXY, getDataFolder()));
|
||||
TAB.getInstance().load();
|
||||
Metrics metrics = new Metrics(this, 10535);
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
|
||||
TAB.create(new BungeePlatform(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,9 +9,13 @@ import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.TabExecutor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Command handler for plugin's command on BungeeCord.
|
||||
*/
|
||||
public class BungeeTabCommand extends Command implements TabExecutor {
|
||||
|
||||
/**
|
||||
@@ -22,7 +26,7 @@ public class BungeeTabCommand extends Command implements TabExecutor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
public void execute(@NotNull CommandSender sender, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
|
||||
sender.sendMessage(new TextComponent(EnumChatFormat.color(message)));
|
||||
@@ -38,11 +42,12 @@ public class BungeeTabCommand extends Command implements TabExecutor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
|
||||
@NotNull
|
||||
public Iterable<String> onTabComplete(@NotNull CommandSender sender, @NotNull String[] args) {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof ProxiedPlayer) {
|
||||
p = TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId());
|
||||
if (p == null) return new ArrayList<>(); //player not loaded correctly
|
||||
if (p == null) return Collections.emptyList(); //player not loaded correctly
|
||||
}
|
||||
return TAB.getInstance().getCommand().complete(p, args);
|
||||
}
|
||||
|
||||
@@ -1,71 +1,62 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1193;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList17;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList18;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.Protocol;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for BungeeCord
|
||||
*/
|
||||
@Getter
|
||||
public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
/** Inaccessible bungee internals */
|
||||
private static @Nullable Object directionData;
|
||||
private static @Nullable Method getId;
|
||||
|
||||
/** Flag tracking plugin presence */
|
||||
private static final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
|
||||
|
||||
static {
|
||||
try {
|
||||
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
|
||||
getId = ReflectionUtils.setAccessible(directionData.getClass().getDeclaredMethod("getId", Class.class, int.class));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Player's scoreboard */
|
||||
@Getter private final @NotNull Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
|
||||
@NotNull
|
||||
private final BungeeScoreboard scoreboard = new BungeeScoreboard(this);
|
||||
|
||||
/** Player's tab list based on version */
|
||||
private final @NotNull TabList tabList1_7 = new BungeeTabList17(this);
|
||||
private final @NotNull TabList tabList1_8 = new BungeeTabList18(this);
|
||||
private final @NotNull TabList tabList1_19_3 = new BungeeTabList1193(this);
|
||||
@NotNull
|
||||
private final BungeeTabList tabList1_7 = new BungeeTabList17(this);
|
||||
|
||||
@Getter private final @NotNull BossBar bossBar = new BungeeBossBar(this);
|
||||
@NotNull
|
||||
private final BungeeTabList tabList1_8 = new BungeeTabList18(this);
|
||||
|
||||
@NotNull
|
||||
private final BungeeTabList tabList1_19_3 = new BungeeTabList1193(this);
|
||||
|
||||
/** Player's boss bar view */
|
||||
@NotNull
|
||||
private final BossBar bossBar = new BungeeBossBar(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance for given player
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param p
|
||||
* BungeeCord player
|
||||
*/
|
||||
public BungeeTabPlayer(@NotNull ProxiedPlayer p) {
|
||||
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
|
||||
public BungeeTabPlayer(@NotNull BungeePlatform platform, @NotNull ProxiedPlayer p) {
|
||||
super(platform, p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,44 +69,46 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
return getPlayer().getPing();
|
||||
}
|
||||
|
||||
public void sendPacket(@NotNull Object nmsPacket) {
|
||||
getPlayer().unsafe().sendPacket((DefinedPacket) nmsPacket);
|
||||
@Override
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendMessage((BaseComponent) message.convert(getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
getPlayer().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TabList.Skin getSkin() {
|
||||
@Nullable
|
||||
public TabList.Skin getSkin() {
|
||||
LoginResult loginResult = ((InitialHandler)getPlayer().getPendingConnection()).getLoginProfile();
|
||||
if (loginResult == null) return null;
|
||||
Property[] properties = loginResult.getProperties();
|
||||
if (properties == null || properties.length == 0) return null;
|
||||
if (properties == null || properties.length == 0) return null; //Offline mode
|
||||
return new TabList.Skin(properties[0].getValue(), properties[0].getSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ProxiedPlayer getPlayer() {
|
||||
@NotNull
|
||||
public ProxiedPlayer getPlayer() {
|
||||
return (ProxiedPlayer) player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns packet ID for this player of provided packet class
|
||||
*
|
||||
* @param clazz
|
||||
* packet class
|
||||
* @return packet ID
|
||||
*/
|
||||
public int getPacketId(@NotNull Class<? extends DefinedPacket> clazz) {
|
||||
if (getId == null) return -1;
|
||||
@Override
|
||||
public BungeePlatform getPlatform() {
|
||||
return (BungeePlatform) platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginMessage(byte[] message) {
|
||||
if (!getPlayer().isConnected()) return;
|
||||
try {
|
||||
return (int) getId.invoke(directionData, clazz, getPlayer().getPendingConnection().getVersion());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
|
||||
" with client version " + getPlayer().getPendingConnection().getVersion(), e);
|
||||
return -1;
|
||||
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
|
||||
} catch (NullPointerException BungeeCordBug) {
|
||||
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
|
||||
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
|
||||
// at net.md_5.bungee.ServerConnection.sendPacketQueued(ServerConnection.java:48)
|
||||
// at net.md_5.bungee.ServerConnection.sendData(ServerConnection.java:70)
|
||||
if (TAB.getInstance().getConfiguration().isDebugMode()) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to deliver plugin message to player " + getName() +
|
||||
" (online = " + getPlayer().isConnected() + ")", BungeeCordBug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,35 +120,45 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
* @return Player's current protocol version
|
||||
*/
|
||||
@Override
|
||||
public @NotNull ProtocolVersion getVersion() {
|
||||
@NotNull
|
||||
public ProtocolVersion getVersion() {
|
||||
return ProtocolVersion.fromNetworkId(getPlayer().getPendingConnection().getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
//noinspection ConstantConditions
|
||||
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
|
||||
if (PremiumVanishHook.getInstance() != null && PremiumVanishHook.getInstance().isVanished(this)) return true;
|
||||
return super.isVanished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer().isConnected();
|
||||
@NotNull
|
||||
public BungeeTabList getTabList() {
|
||||
int version = getPlayer().getPendingConnection().getVersion();
|
||||
if (version >= ProtocolVersion.V1_19_3.getNetworkId()) return tabList1_19_3;
|
||||
if (version >= ProtocolVersion.V1_8.getNetworkId()) return tabList1_8;
|
||||
return tabList1_7;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TabList getTabList() {
|
||||
return getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() ?
|
||||
tabList1_19_3 : getVersion().getMinorVersion() >= 8 ? tabList1_8 : tabList1_7;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginMessage(byte[] message) {
|
||||
if (getPlayer().getServer() == null) {
|
||||
TAB.getInstance().getErrorManager().printError("Skipped plugin message send to " + getName() + ", because player is not" +
|
||||
"connected to any server (message=" + new String(message) + ")");
|
||||
return;
|
||||
/**
|
||||
* Sends packet to the player. If BungeeCord supports 1.20.2+, new packet queue method is used
|
||||
* to avoid error when sending packet in configuration phase.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
public void sendPacket(@NotNull DefinedPacket packet) {
|
||||
if (!getPlayer().isConnected()) return;
|
||||
try {
|
||||
((UserConnection)getPlayer()).sendPacketQueued(packet);
|
||||
} catch (NullPointerException BungeeCordBug) {
|
||||
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
|
||||
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
|
||||
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
|
||||
if (TAB.getInstance().getConfiguration().isDebugMode()) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to deliver packet to player " + getName() +
|
||||
" (online = " + getPlayer().isConnected() + "): " + packet, BungeeCordBug);
|
||||
}
|
||||
}
|
||||
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
|
||||
}
|
||||
}
|
||||
+12
-5
@@ -1,25 +1,32 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
package me.neznamy.tab.platforms.bungeecord.features;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Redis implementation for BungeeCord
|
||||
* RedisBungee implementation for BungeeCord
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class RedisBungeeSupport extends RedisSupport implements Listener {
|
||||
public class BungeeRedisSupport extends RedisSupport implements Listener {
|
||||
|
||||
/** Plugin reference for registering listener */
|
||||
private final @NotNull Plugin plugin;
|
||||
@NotNull
|
||||
private final BungeeTAB plugin;
|
||||
|
||||
/**
|
||||
* Listens to messages coming from other proxies.
|
||||
*
|
||||
* @param e
|
||||
* Message event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onMessage(@NotNull PubSubMessageEvent e) {
|
||||
if (!e.getChannel().equals(TabConstants.REDIS_CHANNEL_NAME)) return;
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.hook;
|
||||
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.Platform;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* PremiumVanish hook for BungeeCord.
|
||||
*/
|
||||
public class BungeePremiumVanishHook extends PremiumVanishHook {
|
||||
|
||||
private final boolean canSeeEnabled;
|
||||
|
||||
/**
|
||||
* Constructs new instance. If using an old version of PV, warn
|
||||
* is printed into the platform's logger.
|
||||
*
|
||||
* @param platform
|
||||
* Platform to print potential warn to
|
||||
*/
|
||||
public BungeePremiumVanishHook(@NotNull Platform platform) {
|
||||
if (ReflectionUtils.methodExists(BungeeVanishAPI.class, "canSee", ProxiedPlayer.class, ProxiedPlayer.class)) {
|
||||
canSeeEnabled = true;
|
||||
} else {
|
||||
canSeeEnabled = false;
|
||||
platform.logWarn(new SimpleComponent("Detected an outdated version of " +
|
||||
"PremiumVanish with limited API. Vanish compatibility " +
|
||||
"may not work as expected. Update PremiumVanish to version 2.7.11+ for optimal experience."));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
//noinspection ConstantValue
|
||||
return canSeeEnabled && BungeeVanishAPI.canSee(((BungeeTabPlayer)viewer).getPlayer(), ((BungeeTabPlayer)target).getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished(@NotNull TabPlayer player) {
|
||||
try {
|
||||
return BungeeVanishAPI.isInvisible(((BungeeTabPlayer)player).getPlayer());
|
||||
} catch (IllegalStateException ignored) {
|
||||
// PV Bug: PremiumVanish must be enabled to use its API
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
|
||||
import net.md_5.bungee.tab.ServerUnique;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Abstract TabList class for BungeeCord containing
|
||||
* common code for all implementations.
|
||||
*/
|
||||
public abstract class BungeeTabList extends TabList<BungeeTabPlayer, BaseComponent> {
|
||||
|
||||
/** Pointer to UUIDs in player's TabList */
|
||||
private final Collection<UUID> uuids;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
protected BungeeTabList(@NonNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
uuids = (Collection<UUID>) ReflectionUtils.getField(ServerUnique.class, "uuids").get(((UserConnection)player.getPlayer()).getTabListHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull BaseComponent header, @NonNull BaseComponent footer) {
|
||||
player.sendPacket(new PlayerListHeaderFooter(header, footer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Item} with given UUID.
|
||||
*
|
||||
* @param id
|
||||
* UUID to use
|
||||
* @return New {@link Item} with given UUID.
|
||||
*/
|
||||
@NonNull
|
||||
public Item item(@NonNull UUID id) {
|
||||
Item item = new Item();
|
||||
item.setUuid(id);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts entry data to item.
|
||||
*
|
||||
* @param id
|
||||
* Entry UUID
|
||||
* @param name
|
||||
* Entry name
|
||||
* @param skin
|
||||
* Entry skin
|
||||
* @param latency
|
||||
* Entry latency
|
||||
* @param gameMode
|
||||
* Entry game mode
|
||||
* @param displayName
|
||||
* Entry display name
|
||||
* @return Converted item from parameters
|
||||
*/
|
||||
@NonNull
|
||||
public Item entryToItem(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable BaseComponent displayName) {
|
||||
Item item = item(id);
|
||||
item.setUsername(name);
|
||||
item.setDisplayName(displayName);
|
||||
item.setGamemode(gameMode);
|
||||
item.setListed(true);
|
||||
item.setPing(latency);
|
||||
if (skin != null) {
|
||||
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, skin.getValue(), skin.getSignature())});
|
||||
} else {
|
||||
item.setProperties(new Property[0]);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds given UUID to BungeeCord's tablist uuid tracker.
|
||||
*
|
||||
* @param id
|
||||
* UUID to add
|
||||
*/
|
||||
public void addUuid(@NonNull UUID id) {
|
||||
uuids.add(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given UUID from BungeeCord's tablist uuid tracker.
|
||||
*
|
||||
* @param id
|
||||
* UUID to remove
|
||||
*/
|
||||
public void removeUuid(@NonNull UUID id) {
|
||||
uuids.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PlayerListItem) {
|
||||
PlayerListItem listItem = (PlayerListItem) packet;
|
||||
for (PlayerListItem.Item item : listItem.getItems()) {
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
BaseComponent expectedDisplayName = getExpectedDisplayName(item.getUuid());
|
||||
if (expectedDisplayName != null) item.setDisplayName(expectedDisplayName);
|
||||
}
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(player, item.getUuid(), item.getPing()));
|
||||
}
|
||||
if (listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, item.getUuid(), item.getUsername());
|
||||
}
|
||||
}
|
||||
} else if (packet instanceof PlayerListItemUpdate) {
|
||||
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
|
||||
for (PlayerListItem.Item item : update.getItems()) {
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
|
||||
BaseComponent expectedDisplayName = getExpectedDisplayName(item.getUuid());
|
||||
if (expectedDisplayName != null) item.setDisplayName(expectedDisplayName);
|
||||
}
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
|
||||
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(player, item.getUuid(), item.getPing()));
|
||||
}
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, item.getUuid(), item.getUsername());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return uuids.contains(entry);
|
||||
}
|
||||
}
|
||||
+39
-50
@@ -1,91 +1,80 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItemRemove;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.19.3+ players using the new tab list packets.
|
||||
* Because BungeeCord does not have a TabList API, we need to use packets.
|
||||
* They are sent using an internal BungeeCord method that keeps track of them,
|
||||
* so they are removed on server switch to secure parity with Velocity.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeTabList1193 implements TabList {
|
||||
public class BungeeTabList1193 extends BungeeTabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
private final BungeeTabPlayer player;
|
||||
/** Map of actions to prevent creating new EnumSet on each packet send */
|
||||
private static final Map<Action, EnumSet<PlayerListItemUpdate.Action>> actions = new EnumMap<>(Action.class);
|
||||
|
||||
static {
|
||||
actions.put(Action.ADD_PLAYER, EnumSet.allOf(PlayerListItemUpdate.Action.class));
|
||||
actions.put(Action.UPDATE_GAME_MODE, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE));
|
||||
actions.put(Action.UPDATE_DISPLAY_NAME, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME));
|
||||
actions.put(Action.UPDATE_LATENCY, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public BungeeTabList1193(@NonNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
removeUuid(entry);
|
||||
PlayerListItemRemove remove = new PlayerListItemRemove();
|
||||
remove.setUuids(new UUID[]{entry});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(remove);
|
||||
player.sendPacket(remove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable BaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
|
||||
item.setDisplayName(displayName);
|
||||
sendPacket(Action.UPDATE_DISPLAY_NAME, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
Item item = item(entry);
|
||||
item.setPing(latency);
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY), item);
|
||||
sendPacket(Action.UPDATE_LATENCY, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
Item item = item(entry);
|
||||
item.setGamemode(gameMode);
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE), item);
|
||||
sendPacket(Action.UPDATE_GAME_MODE, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
Item item = item(entry.getUniqueId());
|
||||
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
|
||||
item.setGamemode(entry.getGameMode());
|
||||
item.setListed(true);
|
||||
item.setPing(entry.getLatency());
|
||||
if (entry.getSkin() != null) {
|
||||
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
|
||||
} else {
|
||||
item.setProperties(new Property[0]);
|
||||
}
|
||||
item.setUsername(entry.getName());
|
||||
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), item);
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable BaseComponent displayName) {
|
||||
addUuid(id);
|
||||
sendPacket(Action.ADD_PLAYER, entryToItem(id, name, skin, latency, gameMode, displayName));
|
||||
}
|
||||
|
||||
public @NotNull Item item(@NotNull UUID id) {
|
||||
Item item = new Item();
|
||||
item.setUuid(id);
|
||||
return item;
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull EnumSet<PlayerListItemUpdate.Action> actions, @NotNull Item item) {
|
||||
private void sendPacket(@NonNull Action action, @NonNull Item item) {
|
||||
PlayerListItemUpdate packet = new PlayerListItemUpdate();
|
||||
packet.setActions(actions);
|
||||
packet.setActions(actions.get(action));
|
||||
packet.setItems(new Item[]{item});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.getPlayer().setTabHeader(header.toBungeeComponent(player.getVersion()), footer.toBungeeComponent(player.getVersion()));
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
+48
-33
@@ -1,12 +1,12 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -19,24 +19,32 @@ import java.util.UUID;
|
||||
* names are changed by removing old entry and adding a new one, which
|
||||
* requires further tracking than just the UUID.<p>
|
||||
* Because BungeeCord does not have a TabList API, we need to use packets.
|
||||
* They are sent using an internal BungeeCord method that keeps track of them,
|
||||
* so they are removed on server switch to secure parity with Velocity.
|
||||
* While BungeeCord itself does not support 1.7, some of its forks do.
|
||||
* This was tested on FlameCord fork.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeTabList17 implements TabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
private final BungeeTabPlayer player;
|
||||
public class BungeeTabList17 extends BungeeTabList {
|
||||
|
||||
/** Because entries are identified by names and not uuids on 1.7 */
|
||||
@NonNull
|
||||
private final Map<UUID, String> userNames = new HashMap<>();
|
||||
private final Map<UUID, String> displayNames = new HashMap<>();
|
||||
|
||||
@NonNull
|
||||
private final Map<UUID, BaseComponent> displayNames = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public BungeeTabList17(@NonNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
removeUuid(entry);
|
||||
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
|
||||
|
||||
// Remove from map
|
||||
@@ -45,49 +53,56 @@ public class BungeeTabList17 implements TabList {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable BaseComponent displayName) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
|
||||
addEntry(new Entry.Builder(entry).displayName(displayName).name(userNames.get(entry)).build());
|
||||
addEntry0(entry, userNames.get(entry), null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.UPDATE_LATENCY, createItem(null, displayNames.get(entry), latency));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {/*Added in 1.8*/}
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {/*Added in 1.8*/}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
String displayNameString = entry.getDisplayName() == null ? String.valueOf(entry.getName()) : entry.getDisplayName().toLegacyText();
|
||||
if (displayNameString.length() > 16) displayNameString = displayNameString.substring(0, 16); // 16 character limit
|
||||
update(PlayerListItem.Action.ADD_PLAYER, createItem(entry.getName(), displayNameString, entry.getLatency()));
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable BaseComponent displayName) {
|
||||
addUuid(id);
|
||||
update(PlayerListItem.Action.ADD_PLAYER, createItem(name, displayName == null ? new TextComponent(name) : displayName, latency));
|
||||
|
||||
// Add to map
|
||||
userNames.put(entry.getUniqueId(), entry.getName());
|
||||
displayNames.put(entry.getUniqueId(), displayNameString);
|
||||
userNames.put(id, name);
|
||||
displayNames.put(id, displayName);
|
||||
}
|
||||
|
||||
private void update(@NotNull PlayerListItem.Action action, @NotNull PlayerListItem.Item item) {
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull BaseComponent header, @NonNull BaseComponent footer) {
|
||||
// Not available on 1.7
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseComponent toComponent(@NonNull TabComponent component) {
|
||||
String displayNameString = component.toLegacyText();
|
||||
if (displayNameString.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7)
|
||||
displayNameString = displayNameString.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
return TabComponent.optimized(displayNameString).convert(player.getVersion());
|
||||
}
|
||||
|
||||
private void update(@NonNull PlayerListItem.Action action, @NonNull PlayerListItem.Item item) {
|
||||
PlayerListItem packet = new PlayerListItem();
|
||||
packet.setAction(action);
|
||||
packet.setItems(new PlayerListItem.Item[]{item});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
|
||||
private PlayerListItem.Item createItem(@Nullable String username, @NonNull BaseComponent displayName, int latency) {
|
||||
PlayerListItem.Item item = new PlayerListItem.Item();
|
||||
item.setUsername(username);
|
||||
item.setDisplayName(displayName);
|
||||
item.setPing(latency);
|
||||
item.setDisplayName(displayName);
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
// Added in 1.8
|
||||
}
|
||||
}
|
||||
Loaded 100 of 402 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user