mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-13 12:42:06 -05:00
Compare commits
2566
Commits
2.8.11-pre19
...
v4.1.4
| 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 | ||
|
|
af2700798e | ||
|
|
f9c280bbdc | ||
|
|
6ee31c2c3f | ||
|
|
cbd944f6f2 | ||
|
|
cc20748f31 | ||
|
|
c9545165aa | ||
|
|
05c10bf2e7 | ||
|
|
67aeae2094 | ||
|
|
1cc85a0647 | ||
|
|
988c0f8b4c | ||
|
|
62306fa5d6 | ||
|
|
641c5a49c3 | ||
|
|
5637402435 | ||
|
|
728d6c9a25 | ||
|
|
22d3ba2877 | ||
|
|
d1e032e010 | ||
|
|
80c4316826 | ||
|
|
3addeba256 | ||
|
|
c52db733b7 | ||
|
|
07a492e353 | ||
|
|
3c1ec478c2 | ||
|
|
f3f614c3de | ||
|
|
a6627ac6c6 | ||
|
|
8d1040518f | ||
|
|
ca5d17e6ec | ||
|
|
b9fab42510 | ||
|
|
22f1ef43c4 | ||
|
|
ffbd4bf26b | ||
|
|
4d6f93e975 | ||
|
|
cb45cff77f | ||
|
|
4718040345 | ||
|
|
afffefcce1 | ||
|
|
4580f47563 | ||
|
|
e51f4bb3a1 | ||
|
|
e8c8d1dee7 | ||
|
|
3559afaef7 | ||
|
|
fc29420eb3 | ||
|
|
023cd3fb75 | ||
|
|
2781d9f232 | ||
|
|
d31e5de449 | ||
|
|
245fcdf917 | ||
|
|
e3ef3ffa68 | ||
|
|
95c3608c3f | ||
|
|
7c2236dc96 | ||
|
|
b83ef0e352 | ||
|
|
2865871d04 | ||
|
|
7f7b76a11e | ||
|
|
52569e4c65 | ||
|
|
0a0ae6c83b | ||
|
|
6c8a41388e | ||
|
|
fe94e33d85 | ||
|
|
ca1be457cf | ||
|
|
550b6350ea | ||
|
|
76470c880d | ||
|
|
a315bd88b3 | ||
|
|
bb62930525 | ||
|
|
b7490f0002 | ||
|
|
42f3851339 | ||
|
|
34f39ac8d5 | ||
|
|
8c16330119 | ||
|
|
8323c21c3e | ||
|
|
2a3ca932a4 | ||
|
|
e339928caa | ||
|
|
3eeb025f23 | ||
|
|
f19c1f1baa | ||
|
|
003e6a6adb | ||
|
|
95d435d61c | ||
|
|
bbc4e1d767 | ||
|
|
ae46adf8b9 | ||
|
|
461ef988d3 | ||
|
|
307d21cc85 | ||
|
|
e2beac6570 | ||
|
|
75e455f078 | ||
|
|
19c832d370 | ||
|
|
9cd4afe1d4 | ||
|
|
3b00f07f78 | ||
|
|
0b5008c087 | ||
|
|
76f210db03 | ||
|
|
85c8d9f1b9 | ||
|
|
a795fc7f77 | ||
|
|
7742ab8220 | ||
|
|
3e235a8f18 | ||
|
|
30f9888f6a | ||
|
|
d1f8886e2b | ||
|
|
3c037f9aa6 | ||
|
|
c70346c57c | ||
|
|
bd40dd85af | ||
|
|
eacd929b0f | ||
|
|
0ff5f17b7d | ||
|
|
57fa01151e | ||
|
|
e7bc1fc74b | ||
|
|
897f35ab53 | ||
|
|
7f8b738a69 | ||
|
|
079302fb05 | ||
|
|
630fcc9e50 | ||
|
|
c1601ca487 | ||
|
|
d035b9cb80 | ||
|
|
8db5e4d8ba | ||
|
|
4d51bb39d7 | ||
|
|
7eb0231e08 | ||
|
|
6412461576 | ||
|
|
5732eb366b | ||
|
|
ed3df8bb68 | ||
|
|
787f531203 | ||
|
|
cf5d888901 | ||
|
|
1969e5f755 | ||
|
|
8d12ee0958 | ||
|
|
c3bbc89c5b | ||
|
|
625c7cf3c2 | ||
|
|
14f0321992 | ||
|
|
e5294c596a | ||
|
|
c83290a99a | ||
|
|
8c66310af0 | ||
|
|
5ae91bf89e | ||
|
|
d5dc1b7bdb | ||
|
|
864eee3349 | ||
|
|
88932e3a5e | ||
|
|
4ebc60ff00 | ||
|
|
b0cb63b05d | ||
|
|
1db40ee2d5 | ||
|
|
fd3f0ebd21 | ||
|
|
ce73069345 | ||
|
|
088f2748e3 | ||
|
|
d9a8c69f43 | ||
|
|
16ff503b6a | ||
|
|
68c78b9c73 | ||
|
|
0c9d78a644 | ||
|
|
99d22642e2 | ||
|
|
dc762eacf8 | ||
|
|
5a44f30140 | ||
|
|
82e86b0f8a | ||
|
|
9c4eb4359c | ||
|
|
827c380463 | ||
|
|
7c4fc324c8 | ||
|
|
46215f3304 | ||
|
|
1accc21365 | ||
|
|
b74c2f62f7 | ||
|
|
9d14c0c923 | ||
|
|
650fda5781 | ||
|
|
286ccf5836 | ||
|
|
dfa310666c | ||
|
|
06595241ab | ||
|
|
0f80dd1cfd | ||
|
|
7bc5be264c | ||
|
|
e31784fb1e | ||
|
|
ae156f424b | ||
|
|
cfe0b3883e | ||
|
|
fc6beaa0a5 | ||
|
|
5f2140b147 | ||
|
|
c497bdff39 | ||
|
|
7c84b6432e | ||
|
|
3f787c3b0b | ||
|
|
2046a39e99 | ||
|
|
2ccf89e396 | ||
|
|
efaebc3cb0 | ||
|
|
d7c952cc36 | ||
|
|
1d3ea37129 | ||
|
|
1e9c47398d | ||
|
|
5a1f48f1a5 | ||
|
|
ed65554d30 | ||
|
|
0f19970401 | ||
|
|
b419499710 | ||
|
|
14a5f30b74 | ||
|
|
302cdf200f | ||
|
|
bdde257cac | ||
|
|
73af013bca | ||
|
|
e421427007 | ||
|
|
169a180d68 | ||
|
|
16a19bf578 | ||
|
|
5b39204a8a | ||
|
|
b2bb125248 | ||
|
|
980354104b | ||
|
|
b27571dcaa | ||
|
|
b215bf9000 | ||
|
|
ab75b13b3f | ||
|
|
31cd2aeb08 | ||
|
|
8b6b33b314 | ||
|
|
1b66257ece | ||
|
|
bed9fcf212 | ||
|
|
692f309750 | ||
|
|
2c9c9b502a | ||
|
|
3da8fd2a77 | ||
|
|
ba6826f3d1 | ||
|
|
68b3629437 | ||
|
|
8bc5962d1c | ||
|
|
b8aa1982e4 | ||
|
|
e0a427732e | ||
|
|
8d1f60f5fa | ||
|
|
5cd7fdf385 | ||
|
|
e54d342ac5 | ||
|
|
9019261c2a | ||
|
|
b1a5092053 | ||
|
|
ef8bdbb40f | ||
|
|
0d67826b3d | ||
|
|
6c99e28d51 | ||
|
|
13931aafa5 | ||
|
|
089e7ce8b5 | ||
|
|
54287c8a29 | ||
|
|
5ebc5dee21 | ||
|
|
bedbdbcca9 | ||
|
|
a1c35adf4c | ||
|
|
81b6a9be8b | ||
|
|
88eee58329 | ||
|
|
2f02a5bda4 | ||
|
|
ba716df634 | ||
|
|
988292b82a | ||
|
|
d52cfdd6e6 | ||
|
|
b13a49539f | ||
|
|
32a39a361c | ||
|
|
0e2e9b9ae6 | ||
|
|
331faefde2 | ||
|
|
1603ebe9fc | ||
|
|
bad4359469 | ||
|
|
a860b463ed | ||
|
|
b3b0a96bb8 | ||
|
|
c211724d13 | ||
|
|
0fb9a832ca | ||
|
|
7ce1a97c93 | ||
|
|
1d461c1adf | ||
|
|
5ec09c37bc | ||
|
|
95a20766c3 | ||
|
|
87feb1c2bf | ||
|
|
8c732ac7e9 | ||
|
|
977c1f4d74 | ||
|
|
65ac22aadb | ||
|
|
8e7a14bdb8 | ||
|
|
4693a297b6 | ||
|
|
af69dcd9b5 | ||
|
|
71110fc722 | ||
|
|
c2af5632a2 | ||
|
|
399e813ca4 | ||
|
|
daac7c6171 | ||
|
|
940352ef2c | ||
|
|
c70b909c0f | ||
|
|
dcc2eb9217 | ||
|
|
5425b3b6ea | ||
|
|
a90ea3e3fa | ||
|
|
6d52b7b615 | ||
|
|
51635a7322 | ||
|
|
481b33ba55 | ||
|
|
22c785fc99 | ||
|
|
42d4159b69 | ||
|
|
6d95a2f06d | ||
|
|
7f0b1d8a2e | ||
|
|
a3fecd0fea | ||
|
|
f3d6a54a2a | ||
|
|
1d434f2557 | ||
|
|
b3500086e4 | ||
|
|
bf9202af38 | ||
|
|
bd2c683568 | ||
|
|
c03feaab23 | ||
|
|
9cb9ccbd2a | ||
|
|
4606ebd588 | ||
|
|
679be83c5a | ||
|
|
afe868bb95 | ||
|
|
c9ce3aa23e | ||
|
|
11a49c4874 | ||
|
|
65d2a0fba9 | ||
|
|
c1219195c7 | ||
|
|
fca0dfc91b | ||
|
|
7f1cc3063c | ||
|
|
e444c25287 | ||
|
|
476354e407 | ||
|
|
9f84d3f22f | ||
|
|
4608ff360c | ||
|
|
68b12e199d | ||
|
|
4b1151f769 | ||
|
|
6687214f2a | ||
|
|
a57757258e | ||
|
|
3a6060c45d | ||
|
|
ee375c1b60 | ||
|
|
c9d870cca0 | ||
|
|
3ad8015cc1 | ||
|
|
33609c322f | ||
|
|
431b89c487 | ||
|
|
35a146992c | ||
|
|
dedee114c4 | ||
|
|
9d42af0388 | ||
|
|
8f5d81bc33 | ||
|
|
b17f5563dd | ||
|
|
0ab8bb363a | ||
|
|
ac5fca86bd | ||
|
|
28c2c2e193 | ||
|
|
904c0b6a17 | ||
|
|
f3c31bc92c | ||
|
|
6016b84889 | ||
|
|
cfb5cb3a9f | ||
|
|
1746f6b1d0 | ||
|
|
9c3979a0d9 | ||
|
|
810676c69f | ||
|
|
d0892a245a | ||
|
|
f539b1b93b | ||
|
|
adfa64a29f | ||
|
|
d4f86ee79d | ||
|
|
6ebe31b737 | ||
|
|
dcbd5e62d4 | ||
|
|
a7e6e49363 | ||
|
|
d115257ff7 | ||
|
|
d578be276c | ||
|
|
e338f57441 | ||
|
|
c87321b5b0 | ||
|
|
84bb9b449b | ||
|
|
9d613fc24d | ||
|
|
3f2ae3131e | ||
|
|
0e9f53bd58 | ||
|
|
3981815b63 | ||
|
|
3ef99e4e7c | ||
|
|
76767a0958 | ||
|
|
1683943d58 | ||
|
|
cc0dbe8623 | ||
|
|
b056251e07 | ||
|
|
3b352c66e2 | ||
|
|
b4f0ea73c1 | ||
|
|
1c683788b5 | ||
|
|
250597df2d | ||
|
|
0d2d1d5aaa | ||
|
|
061375d1e7 | ||
|
|
5b05614305 | ||
|
|
69db2e0e53 | ||
|
|
00ef364324 | ||
|
|
de68312b6b | ||
|
|
ddac08bcbc | ||
|
|
517e285bb3 | ||
|
|
81a08a6b51 | ||
|
|
4dfef1d032 | ||
|
|
5d38e28cae | ||
|
|
66a12d10c4 | ||
|
|
1a4027c8d5 | ||
|
|
2a0243c51a | ||
|
|
4851d09e70 | ||
|
|
3cdf3accfd | ||
|
|
fd1f094216 | ||
|
|
25ece1ecb8 | ||
|
|
802e65c280 | ||
|
|
882688f056 | ||
|
|
97d60d0ab3 | ||
|
|
9ef99daed1 | ||
|
|
71c85d9d39 | ||
|
|
b981ab46be | ||
|
|
016633e494 | ||
|
|
1db862ab56 | ||
|
|
3563786310 | ||
|
|
ac8851dd6a | ||
|
|
53ab2d9793 | ||
|
|
2a58c86ffc | ||
|
|
fa46d527ae | ||
|
|
667bd38305 | ||
|
|
faba439466 | ||
|
|
c301157758 | ||
|
|
8d09d425a4 | ||
|
|
11f03d5a10 | ||
|
|
83625202a1 | ||
|
|
eb4b715a88 | ||
|
|
89dce941cf | ||
|
|
6371b67e9e | ||
|
|
966e27418a | ||
|
|
4adf7d1734 | ||
|
|
1a4c4a6fe8 | ||
|
|
7ff36592ae | ||
|
|
1b513412d2 | ||
|
|
f2bb43fbcd | ||
|
|
df5d07d885 | ||
|
|
0ad2f33c4c | ||
|
|
dee48fccb3 | ||
|
|
9ca372ab13 | ||
|
|
58a849a902 | ||
|
|
895072ec35 | ||
|
|
2b2c4d2c39 | ||
|
|
d534b6e2e8 | ||
|
|
783e9d9931 | ||
|
|
a109d9b55b | ||
|
|
5ce59ce44f | ||
|
|
1e9195a9bc | ||
|
|
3099d16468 | ||
|
|
c9c6af346c | ||
|
|
af80a43628 | ||
|
|
dc3c98ec91 | ||
|
|
dffa2c83f6 | ||
|
|
dc66f253a7 | ||
|
|
17498cbfc2 | ||
|
|
96e4d30168 | ||
|
|
c9d1934bb9 | ||
|
|
8bec083194 | ||
|
|
abf047be23 | ||
|
|
4a95399d56 | ||
|
|
885bf5154e | ||
|
|
3e146fc496 | ||
|
|
95710bcaf5 | ||
|
|
edd620f17d | ||
|
|
9c466d8b1e | ||
|
|
b385ab70fb | ||
|
|
140224840b | ||
|
|
2078cd5a5f | ||
|
|
27bae7fdbd | ||
|
|
174b4214de | ||
|
|
4ac524840c | ||
|
|
dd4ce4e2c3 | ||
|
|
2a03d69628 | ||
|
|
c28252e6ce | ||
|
|
da1768d833 | ||
|
|
b58c3e6b47 | ||
|
|
c7575bdc8f | ||
|
|
e275d22061 | ||
|
|
b5600ef22a | ||
|
|
b8e14c9256 | ||
|
|
1af5b9a308 | ||
|
|
2dd719c3a5 | ||
|
|
45d94694cd | ||
|
|
3ce8c6a192 | ||
|
|
7deb86f105 | ||
|
|
d9b4f6a43f | ||
|
|
54a7fb8721 | ||
|
|
d1dcb2f437 | ||
|
|
a92bab617a | ||
|
|
57e34bc595 | ||
|
|
953baa273c | ||
|
|
c403cb3187 | ||
|
|
ffd6dc5bb9 | ||
|
|
0f6504a3dc | ||
|
|
f376fea598 | ||
|
|
4896cdff91 | ||
|
|
b4c87431bc | ||
|
|
1cffbab97d | ||
|
|
9b0711075a | ||
|
|
17a8e902eb | ||
|
|
4bdb5ba245 | ||
|
|
fd7748d280 | ||
|
|
317b0292ff | ||
|
|
b87be649bc | ||
|
|
e2dda54011 | ||
|
|
d17518c39b | ||
|
|
48541dd0bd | ||
|
|
b83b023fee | ||
|
|
60e0c78033 | ||
|
|
a395f7f01f | ||
|
|
09c36ef69e | ||
|
|
9d5fa685a0 | ||
|
|
6794f593fa | ||
|
|
5e9965276f | ||
|
|
1473038955 | ||
|
|
2041ca57f3 | ||
|
|
098782d820 | ||
|
|
87e9a6defd | ||
|
|
3d896ff828 | ||
|
|
bf93240888 | ||
|
|
0b0e30c3c9 | ||
|
|
84d3816bd1 | ||
|
|
04ff606947 | ||
|
|
1cb8e6e238 | ||
|
|
4a2226170d | ||
|
|
1469124cfc | ||
|
|
0826095e73 | ||
|
|
1bd698adde | ||
|
|
1058d269ff | ||
|
|
bacd87b78e | ||
|
|
5befbe8780 | ||
|
|
e159b2adae | ||
|
|
52f7418853 | ||
|
|
76061f7ccd | ||
|
|
7d39e21eae | ||
|
|
fee3723474 | ||
|
|
2f0e4c6888 | ||
|
|
b04cb6d3c5 | ||
|
|
a9228a698a | ||
|
|
27102495d8 | ||
|
|
d1bf96ab33 | ||
|
|
3360dbb468 | ||
|
|
d224e7584e | ||
|
|
6373540076 | ||
|
|
5f78aca997 | ||
|
|
145d944d33 | ||
|
|
ac91940e3b | ||
|
|
ef8452be73 | ||
|
|
47e0810697 | ||
|
|
1f30f225c7 | ||
|
|
fcbfd60ddb | ||
|
|
46f9033713 | ||
|
|
7e65018a0e | ||
|
|
9ef3485968 | ||
|
|
257f8f1145 | ||
|
|
8d65cbb0e5 | ||
|
|
20d221e3ca | ||
|
|
94072acf10 | ||
|
|
0422cc2597 | ||
|
|
58f61e645b | ||
|
|
5f416bd9e5 | ||
|
|
64528c913c | ||
|
|
357e7c6a49 | ||
|
|
81dd202053 | ||
|
|
189797aa73 | ||
|
|
1ae2ef7cad | ||
|
|
de14e14d33 | ||
|
|
19892c14c1 | ||
|
|
2e00c4c002 | ||
|
|
97aa367629 | ||
|
|
87cc9a030b | ||
|
|
8ba83d1cb1 | ||
|
|
9f0e60bd47 | ||
|
|
93b2a25b2e | ||
|
|
4242d7a839 | ||
|
|
0ddb99bd46 | ||
|
|
969672e3ad | ||
|
|
2423fbb150 | ||
|
|
a99928dbf5 | ||
|
|
12f5dd52b4 | ||
|
|
2bd105446e | ||
|
|
6d85875fde | ||
|
|
42810aa490 | ||
|
|
bd31521bd3 | ||
|
|
ec0deb8378 | ||
|
|
ae66b6e9a1 | ||
|
|
3b86becebd | ||
|
|
4a8d86825b | ||
|
|
54a3bd81b4 | ||
|
|
b6768cca99 | ||
|
|
60c414deed | ||
|
|
009283a83f | ||
|
|
4a430b97df | ||
|
|
722333da2e | ||
|
|
1215af5262 | ||
|
|
254088c37b | ||
|
|
cb2ce4acca | ||
|
|
872adca8fd | ||
|
|
f37cc0bdd8 | ||
|
|
4063578ac3 | ||
|
|
2528de7eeb | ||
|
|
37caed3123 | ||
|
|
27fe9dfb12 | ||
|
|
0169cbd4d2 | ||
|
|
ed760aec08 | ||
|
|
858cbcefeb | ||
|
|
8a87d581a9 | ||
|
|
6d642f4e51 | ||
|
|
aca63640bc | ||
|
|
1845a35315 | ||
|
|
cab5f89a95 | ||
|
|
1777d8bf01 | ||
|
|
91b0bfaa9d | ||
|
|
f1cf994d10 | ||
|
|
69957be933 | ||
|
|
b78eb5efb6 | ||
|
|
3577ad1964 | ||
|
|
4510491177 | ||
|
|
01b3a12377 | ||
|
|
64ae9863fb | ||
|
|
ecd315a407 | ||
|
|
678eed288e | ||
|
|
9510455cb1 | ||
|
|
e925ce23d0 | ||
|
|
c68cad6a1e | ||
|
|
01d6eda4fb | ||
|
|
73e7119bbc | ||
|
|
cea781382a | ||
|
|
cf25b84e86 | ||
|
|
86e1d22390 | ||
|
|
1625be111f | ||
|
|
00cda3b2aa | ||
|
|
85a00a4436 | ||
|
|
37aec1a497 | ||
|
|
ef637616ee | ||
|
|
af3b5067fb | ||
|
|
53e5b15f96 | ||
|
|
6a3afae607 | ||
|
|
2e05c7d9d1 | ||
|
|
fb698a04b7 | ||
|
|
52bdf30ddb | ||
|
|
a92f07aa40 | ||
|
|
003ff7f99d | ||
|
|
47b9806ae7 | ||
|
|
d2a7c65e08 | ||
|
|
e232336a5f | ||
|
|
8816c75212 | ||
|
|
f9d54751ba | ||
|
|
43d41ee761 | ||
|
|
4512ba8d5e | ||
|
|
8b55bca564 | ||
|
|
d8d54970d3 | ||
|
|
15ea4482f9 | ||
|
|
87f9f38eac | ||
|
|
dba559f041 | ||
|
|
c610b6f323 | ||
|
|
a5fb900e19 | ||
|
|
09d6e455f5 | ||
|
|
e68097b0f4 | ||
|
|
0744fed953 | ||
|
|
de0adf4067 | ||
|
|
76bf2b3efc | ||
|
|
c089badeb4 | ||
|
|
b3b99d366a | ||
|
|
4a36486f65 | ||
|
|
75126d00d9 | ||
|
|
17ebf95d5b | ||
|
|
c764420cfe | ||
|
|
28f05fc2af | ||
|
|
a420892887 | ||
|
|
d910dc5913 | ||
|
|
c9c1cbc349 | ||
|
|
fd4704f75e | ||
|
|
5400ca637f | ||
|
|
79d2610117 | ||
|
|
6a51f4cb9d | ||
|
|
c190776665 | ||
|
|
3765a1232e | ||
|
|
28866eba21 | ||
|
|
08adee3dc3 | ||
|
|
8a1adaf2cd | ||
|
|
fdb9accaa6 | ||
|
|
4f7656748b | ||
|
|
03e4495ac7 | ||
|
|
01af5cb9e1 | ||
|
|
8d49f32df2 | ||
|
|
8a955b3bc8 | ||
|
|
ead40cb413 | ||
|
|
5e07f78d4f | ||
|
|
c7d98795bc | ||
|
|
9f637e3d6a | ||
|
|
2d5f074c78 | ||
|
|
3be6ba484d | ||
|
|
d9d74e7a32 | ||
|
|
887306a5ed | ||
|
|
4b2542eb4f | ||
|
|
66bd3baa3e | ||
|
|
bf32004f8f | ||
|
|
bfb2df9c95 | ||
|
|
ddeb05e408 | ||
|
|
9312d61258 | ||
|
|
d44909bc42 | ||
|
|
1d77df335c | ||
|
|
a268dc25f4 | ||
|
|
8c5696faa0 | ||
|
|
94f3ccfb7d | ||
|
|
576329214d | ||
|
|
5c13c2e225 | ||
|
|
1070f85617 | ||
|
|
7c9dc87fcb | ||
|
|
363e58bf91 | ||
|
|
daca3f5b71 | ||
|
|
a7d2ec084b | ||
|
|
167f8cd0ff | ||
|
|
170307f45f | ||
|
|
2f36377780 | ||
|
|
48c565d85d | ||
|
|
86b3cc4044 | ||
|
|
6a2d5091b8 | ||
|
|
dc09e77190 | ||
|
|
4fc139c0c6 | ||
|
|
e5d800f469 | ||
|
|
4879ac28b5 | ||
|
|
bd0badab48 | ||
|
|
5575a26d3b | ||
|
|
d8cbe96949 | ||
|
|
cfb506a7ff | ||
|
|
d5b0ff7f40 | ||
|
|
31d259210c | ||
|
|
39112b8d41 | ||
|
|
4c92286ebb | ||
|
|
a317aeba85 | ||
|
|
dcaf17294e | ||
|
|
0bc734689a | ||
|
|
d0c2c00cc9 | ||
|
|
fd02328af9 | ||
|
|
3b7de31324 | ||
|
|
ebd67dd025 | ||
|
|
4557a39ae2 | ||
|
|
ce1eb299d3 | ||
|
|
20ab3f0a72 | ||
|
|
d61f90c25c | ||
|
|
90474e7592 | ||
|
|
a2e28003bc | ||
|
|
6e409c3dcd | ||
|
|
85a699c8de | ||
|
|
a788d1fb23 | ||
|
|
ab5b72375c | ||
|
|
3a023c51b5 | ||
|
|
0ad91bcafe | ||
|
|
f41ea3bcab | ||
|
|
327dc4b416 | ||
|
|
27e7779456 | ||
|
|
a7b2f5bb22 | ||
|
|
19fcb7c946 | ||
|
|
2706cd5881 | ||
|
|
4e2abd9c5b | ||
|
|
51ba2fa80a | ||
|
|
fd60cd0a21 | ||
|
|
5025372c05 | ||
|
|
4806bd075f | ||
|
|
370d17757a | ||
|
|
a648d7e75f | ||
|
|
518e1c5310 | ||
|
|
11539d8290 | ||
|
|
e4f33b58f7 | ||
|
|
073caa9515 | ||
|
|
2b06b11916 | ||
|
|
032b0f8a41 | ||
|
|
9391363f9b | ||
|
|
467fb2e9c7 | ||
|
|
257893b8de | ||
|
|
e919839de5 | ||
|
|
77d372cbee | ||
|
|
03e9facaf7 | ||
|
|
f3e459d911 | ||
|
|
36feff0e04 | ||
|
|
0320140a1b | ||
|
|
4fb1b1c9f9 | ||
|
|
37f3896a41 | ||
|
|
e1c152d95d | ||
|
|
dffac5deb8 | ||
|
|
8aa3bbfad3 | ||
|
|
5bb4006e55 | ||
|
|
e06a406682 | ||
|
|
a19719be4b | ||
|
|
35856a9f3d | ||
|
|
cf1d5d5ee2 | ||
|
|
f69a845e51 | ||
|
|
db67e93bb8 | ||
|
|
313f089222 | ||
|
|
458c08ded7 | ||
|
|
fe6cc7417f | ||
|
|
181c94a808 | ||
|
|
9ff6f02d8d | ||
|
|
3cf74516d9 | ||
|
|
31f5502c16 | ||
|
|
671d60bd72 | ||
|
|
294e866501 | ||
|
|
96789100e3 | ||
|
|
ea934fc7fb | ||
|
|
d529b7b340 | ||
|
|
092ca7f9f8 | ||
|
|
79f4bdb869 | ||
|
|
b0d2d0f7dc | ||
|
|
cdf58c1dfe | ||
|
|
4680de6022 | ||
|
|
e8f61181b6 | ||
|
|
75e9e007d0 | ||
|
|
3574ef7c0b | ||
|
|
86158036d2 | ||
|
|
e73beb47a3 | ||
|
|
0db5f1dd73 | ||
|
|
6dcdc2aa58 | ||
|
|
64601137e8 | ||
|
|
af7697b670 | ||
|
|
9e4f46088b | ||
|
|
91e71a9b9c | ||
|
|
1198ffb847 | ||
|
|
72116ecd0c | ||
|
|
04ebb7123d | ||
|
|
43096beafe | ||
|
|
2a08c62456 | ||
|
|
b62d74c3fa | ||
|
|
652fb5a6ad | ||
|
|
d1f25cc19f | ||
|
|
dc6a9d9761 | ||
|
|
838d725715 | ||
|
|
c45acb39ca | ||
|
|
957edf2a52 | ||
|
|
d969c6ed0e | ||
|
|
dd6fca961c | ||
|
|
e88a6df456 | ||
|
|
3a4530c999 | ||
|
|
b623dba40a | ||
|
|
e2fd945109 | ||
|
|
e6dc3cda31 | ||
|
|
18d8ff127a | ||
|
|
4a9526451f | ||
|
|
669d89c870 | ||
|
|
39161f2af5 | ||
|
|
be8c33e3fc | ||
|
|
7c1a27f9df | ||
|
|
342cc93dbf | ||
|
|
6f07cf439b | ||
|
|
1d4d00b69b | ||
|
|
54cb3e4d54 | ||
|
|
b0fc2caef9 | ||
|
|
1221509d24 | ||
|
|
17f5f60504 | ||
|
|
a8b0b488c6 | ||
|
|
6d922d5b70 | ||
|
|
1ab62a33e3 | ||
|
|
f9540a1e78 | ||
|
|
a720bf2c8b | ||
|
|
c143eab6fb | ||
|
|
1afb20af8b | ||
|
|
c6f27c839d | ||
|
|
742f1708c5 | ||
|
|
ac9601a48b | ||
|
|
4cd109a5fc | ||
|
|
078554a8b0 | ||
|
|
464e2f4f64 | ||
|
|
b84f55aa18 | ||
|
|
be2f780bfa | ||
|
|
262d8493a6 | ||
|
|
24e12e871b | ||
|
|
a2ee75339c | ||
|
|
9c28c4d63c | ||
|
|
7d57da1424 | ||
|
|
b77b20ba4d | ||
|
|
57ae58f093 | ||
|
|
b29c878688 | ||
|
|
5a5a6c76c1 | ||
|
|
f6a53f537c | ||
|
|
d86ed37208 | ||
|
|
92b945f28d | ||
|
|
cb164f0055 | ||
|
|
acfe37c1b5 | ||
|
|
44ded26df4 | ||
|
|
6dbd92a010 | ||
|
|
f729ca0ad2 | ||
|
|
065490c077 | ||
|
|
da6425dd93 | ||
|
|
250dcbc676 | ||
|
|
de4ca4a88f | ||
|
|
723a0c890e | ||
|
|
7085e7df20 | ||
|
|
09496d60f3 | ||
|
|
bf6bc26a5c | ||
|
|
a7217eae15 | ||
|
|
2a532ef9b7 | ||
|
|
c304ded924 | ||
|
|
4d8c47a505 | ||
|
|
d3e0ccbf34 | ||
|
|
3c82b46148 | ||
|
|
07d7f2cc17 | ||
|
|
54d4f09ab1 | ||
|
|
e8c698b5e8 | ||
|
|
b096906ffd | ||
|
|
5a9cec12fa | ||
|
|
a2ed71751f | ||
|
|
5a3a2df920 | ||
|
|
9d07e669ea | ||
|
|
aa18f1cc35 | ||
|
|
668d78ea64 | ||
|
|
6f62581d6e | ||
|
|
06686ada5c | ||
|
|
4380482606 | ||
|
|
d5112b3d32 | ||
|
|
ef67506640 | ||
|
|
c2ea4d2793 | ||
|
|
f0d8d479bd | ||
|
|
2d46d70c92 | ||
|
|
4320bb7cb4 | ||
|
|
398e8ed5ff | ||
|
|
4e8b804337 | ||
|
|
de7c664e0d | ||
|
|
c7600a2e3d | ||
|
|
8b34e82aab | ||
|
|
c96efe8802 | ||
|
|
0a1bfd3a2e | ||
|
|
755cf9ce43 | ||
|
|
c6aca74396 | ||
|
|
b5b7f3c16c | ||
|
|
293162b484 | ||
|
|
bd543b7b9d | ||
|
|
994fe9f482 | ||
|
|
f657bdb14c | ||
|
|
a4238e1191 | ||
|
|
daf08423b4 | ||
|
|
91367f3d3a | ||
|
|
9dbd52b638 | ||
|
|
22c6be2770 | ||
|
|
fb261c9f90 | ||
|
|
638e35bf90 | ||
|
|
25d13862ae | ||
|
|
1c8f483138 | ||
|
|
92ad215873 | ||
|
|
7ed3cfbb7c | ||
|
|
53e75a27be | ||
|
|
1b67819c3c | ||
|
|
07f8020f79 | ||
|
|
b12d0680d8 | ||
|
|
64247d913e | ||
|
|
3575cdae85 | ||
|
|
f71a542247 | ||
|
|
46cc59138b | ||
|
|
46bfa5112e | ||
|
|
fcdff51783 | ||
|
|
a4be4ae419 | ||
|
|
7083352b9a | ||
|
|
956a0f46c0 | ||
|
|
aea1864627 | ||
|
|
803b87111a | ||
|
|
f64a641b63 | ||
|
|
7f9bb354d7 | ||
|
|
4b7b6284f8 | ||
|
|
bbccd26415 | ||
|
|
bab7c737b3 | ||
|
|
3169787b41 | ||
|
|
f29bbc6657 | ||
|
|
f53cd5ae11 | ||
|
|
38d36b5ab5 | ||
|
|
8f8f596abc | ||
|
|
2afc8abdb2 | ||
|
|
38dd948306 | ||
|
|
e393cf0a10 | ||
|
|
4f451017e3 | ||
|
|
9cb31af48a | ||
|
|
2c17041b27 | ||
|
|
72c75a443e | ||
|
|
1ba6384647 | ||
|
|
e2edf9a37c | ||
|
|
cb0bfd095d | ||
|
|
ec03dd4755 | ||
|
|
de44f5ae42 | ||
|
|
5ee6796172 | ||
|
|
d034fca513 | ||
|
|
f65c3fb29a | ||
|
|
7192bc9d69 | ||
|
|
d7636a05f4 | ||
|
|
bd93301634 | ||
|
|
b0558ba0a2 | ||
|
|
251934bb4b | ||
|
|
f2c9ef5d30 | ||
|
|
15a072bbaa | ||
|
|
8f1c303862 | ||
|
|
c79d2e3e6e | ||
|
|
f45d8d860b | ||
|
|
88f31184a0 | ||
|
|
d5368baab4 | ||
|
|
10f645226c | ||
|
|
322c572cd6 | ||
|
|
fdfeb178b5 | ||
|
|
1c63cdba91 | ||
|
|
289d0db8e3 | ||
|
|
9adacca77f | ||
|
|
7dc6813274 | ||
|
|
77bf1cfbb9 | ||
|
|
8f28350579 | ||
|
|
2d06d0c1d3 | ||
|
|
7e8301afcd | ||
|
|
2600e8f856 | ||
|
|
914748e14e | ||
|
|
21a7b633e7 | ||
|
|
a77d4352e2 | ||
|
|
a7a21e0e51 | ||
|
|
a0a14cd285 | ||
|
|
44b7bedd53 | ||
|
|
409a0808c1 | ||
|
|
4214d02fad | ||
|
|
bc1abb1b58 | ||
|
|
2f2f53ba4d | ||
|
|
cbeb4c151d | ||
|
|
1d5ca70790 | ||
|
|
1f619ff4d3 | ||
|
|
f5acdebe60 | ||
|
|
bd59922204 | ||
|
|
c4d9482c79 | ||
|
|
8faef8f017 | ||
|
|
284161db77 | ||
|
|
78db2b66ef | ||
|
|
0d71643ecb | ||
|
|
9a7a105e7b | ||
|
|
f54d024d97 | ||
|
|
94338bd53f | ||
|
|
1a9ecc0313 | ||
|
|
4baeb7b945 | ||
|
|
baee479fb5 | ||
|
|
5ad4b2e9c4 | ||
|
|
26657cddfc | ||
|
|
947a922f8c | ||
|
|
a2be76184e | ||
|
|
8ddb733dc3 | ||
|
|
94e016129b | ||
|
|
5e653ded5d | ||
|
|
1bf8097c3f | ||
|
|
3d2f11ffd2 | ||
|
|
5e77a22d7a | ||
|
|
1dffcb1dc3 | ||
|
|
edc18cbafb | ||
|
|
2f23194432 | ||
|
|
20709472be | ||
|
|
7a8c6a0cec | ||
|
|
88558e40a7 | ||
|
|
79386b2b6b | ||
|
|
3aad8d3cb5 | ||
|
|
272a6f37ad | ||
|
|
e10081025d | ||
|
|
e03a6d8f11 | ||
|
|
98e7dc0321 | ||
|
|
74438c5d74 | ||
|
|
0c4e548565 | ||
|
|
cc70e01e96 | ||
|
|
7c635748af | ||
|
|
0c874ad56d | ||
|
|
2fb7f80b71 | ||
|
|
3f7c3414ce | ||
|
|
97165db44d | ||
|
|
82cdf04cf0 | ||
|
|
171d899609 | ||
|
|
6432278d5d | ||
|
|
ccaff12451 | ||
|
|
11347c080b | ||
|
|
553676c3f9 | ||
|
|
40b70e7418 | ||
|
|
0ea75019ae | ||
|
|
dbc092df77 | ||
|
|
68049f0dca | ||
|
|
9db5dfc175 | ||
|
|
039a316b3b | ||
|
|
f4b529f1ce | ||
|
|
9c6d606103 | ||
|
|
6cdc06aae7 | ||
|
|
2684654e7b | ||
|
|
25bcc33204 | ||
|
|
e5b3f6215b | ||
|
|
9306eadd90 | ||
|
|
a27fbb5b8b | ||
|
|
cd4c82b0b8 | ||
|
|
aa6179ee9e | ||
|
|
6594b8c2dc | ||
|
|
d4c2cdf003 | ||
|
|
2b529c37f5 | ||
|
|
8599cd925b | ||
|
|
89f37b4b9e | ||
|
|
f3136e85ee | ||
|
|
845743073c | ||
|
|
b2c7bacaf9 | ||
|
|
ed1673f032 | ||
|
|
bac9d443ea | ||
|
|
90e3868c08 | ||
|
|
c428572e01 | ||
|
|
884b5755ec | ||
|
|
32d7366795 | ||
|
|
21bf4a5450 | ||
|
|
8042b02cf5 | ||
|
|
ae7cdd3982 | ||
|
|
9ba945ad76 | ||
|
|
9cd04c3660 | ||
|
|
444bc19781 | ||
|
|
eb08c4ba04 | ||
|
|
5846e24a29 | ||
|
|
9c2c09e433 | ||
|
|
3c4329697b | ||
|
|
af9951d993 | ||
|
|
630f6df2f5 | ||
|
|
fcf07b8e7b | ||
|
|
4824c2f626 | ||
|
|
2b6b2e1b63 | ||
|
|
6926c1e57f | ||
|
|
6a00a7f1ca | ||
|
|
288c3d4c87 | ||
|
|
ac497bcc41 | ||
|
|
7b8d0d6845 | ||
|
|
a9a4589fae | ||
|
|
b8f972a774 | ||
|
|
305e388478 | ||
|
|
97e7848a78 | ||
|
|
d33058a924 | ||
|
|
eda3cc1f17 | ||
|
|
47043ad4ad | ||
|
|
acf20088cc | ||
|
|
374cb2766c | ||
|
|
31685b9ba5 | ||
|
|
722ff4ce52 | ||
|
|
161b375043 | ||
|
|
2c14191cde | ||
|
|
486546918f | ||
|
|
23d08e58a4 | ||
|
|
4c4c34ecaa | ||
|
|
bd3f2fd70a | ||
|
|
b80e42a635 | ||
|
|
9446a034d0 | ||
|
|
da0c6ab3fd | ||
|
|
c1adb27ee5 | ||
|
|
980d3027e3 | ||
|
|
53ba533f9b | ||
|
|
e40827da65 | ||
|
|
b9848b6a96 | ||
|
|
147593b11a | ||
|
|
21652f928b | ||
|
|
5b0352aff0 | ||
|
|
b2da2fa096 | ||
|
|
0cbb01a2a8 | ||
|
|
179840968d | ||
|
|
3b9c1649a1 | ||
|
|
5750c7dbe1 | ||
|
|
7ceca359be | ||
|
|
0f8152a6ed | ||
|
|
b3258bb020 | ||
|
|
4b2e705cbc | ||
|
|
31e41382f8 | ||
|
|
ecda7192dc | ||
|
|
9a48562b9f | ||
|
|
c181b58bc1 | ||
|
|
2624afa2ed | ||
|
|
3690af1e35 | ||
|
|
98966d3b38 | ||
|
|
41fdd16ce1 | ||
|
|
1bc94859c0 | ||
|
|
60f6f36437 | ||
|
|
82267ba16a | ||
|
|
14260deef8 | ||
|
|
8ba7342ccf | ||
|
|
f6b935d78e | ||
|
|
8cf3ae2091 | ||
|
|
e02b997521 | ||
|
|
229cef13c9 | ||
|
|
f503d6f896 | ||
|
|
d851166bb7 | ||
|
|
638a861350 | ||
|
|
20d7e4cbb0 | ||
|
|
36ccedd7c6 | ||
|
|
31854ba7a6 | ||
|
|
2639f07f5c | ||
|
|
b6e13f557f | ||
|
|
6333d90059 | ||
|
|
fea5d0ec56 | ||
|
|
c77f02d1fd | ||
|
|
c498965cae | ||
|
|
cdaa77071d | ||
|
|
6a72678c8d | ||
|
|
bb67fc622f | ||
|
|
5b50c02d93 | ||
|
|
ffec7e3266 | ||
|
|
039c1e7027 | ||
|
|
b27a21b3b8 | ||
|
|
2bcb5f617e | ||
|
|
7bf5294f9f | ||
|
|
c2fd1940f0 | ||
|
|
2d095ce6db | ||
|
|
e88bc36fca | ||
|
|
5613ea4f77 | ||
|
|
9c41599d60 | ||
|
|
5f8dd869e0 | ||
|
|
fb17df0b7e | ||
|
|
900ae83028 | ||
|
|
2c09db25b7 | ||
|
|
004e53fa29 | ||
|
|
e285b143d5 | ||
|
|
f78eb0ffed | ||
|
|
4d761e1d6e | ||
|
|
4a8c975319 | ||
|
|
fe56b7ed2d | ||
|
|
5a74654807 | ||
|
|
533ce4a529 | ||
|
|
4ea3780114 | ||
|
|
0d229ee26c | ||
|
|
29079ecad5 | ||
|
|
ea88401a58 | ||
|
|
298cef4d1f | ||
|
|
a51e9495de | ||
|
|
eead8ca1e2 | ||
|
|
a30712bc38 | ||
|
|
07b443fb21 | ||
|
|
61d927305c | ||
|
|
8f21c2f27d | ||
|
|
9fcf76e0c4 | ||
|
|
773afe2fb5 | ||
|
|
83ce5bd476 | ||
|
|
75a341c240 | ||
|
|
921a793b5c | ||
|
|
c1b92868cd | ||
|
|
94d19d728d | ||
|
|
8ef518f7e2 | ||
|
|
79fd1148a9 | ||
|
|
313ba8d765 | ||
|
|
c12c422357 | ||
|
|
b14c97d18a | ||
|
|
2fe345cb65 | ||
|
|
96ff81370c | ||
|
|
c4a1f41673 | ||
|
|
ca4c8ccb20 | ||
|
|
7471006f8a | ||
|
|
896c709824 | ||
|
|
473a37cb6a | ||
|
|
9e84d96051 | ||
|
|
019466d84d | ||
|
|
c222061529 | ||
|
|
acdd99404c | ||
|
|
799381bff3 | ||
|
|
b3654977ca | ||
|
|
b67d1928b5 | ||
|
|
052748d2b9 | ||
|
|
e265187ff8 | ||
|
|
c622a95550 | ||
|
|
94d71a245b | ||
|
|
a9403269be | ||
|
|
dfe6768eb5 | ||
|
|
d88bf65806 | ||
|
|
30929062d6 | ||
|
|
747e3d692d | ||
|
|
1d3ecbb7ed | ||
|
|
39b88b0dd5 | ||
|
|
d13d3eb055 | ||
|
|
f8f72b9074 | ||
|
|
a9d0b0a0ae | ||
|
|
37bd624537 | ||
|
|
e19f4ed771 | ||
|
|
c828fa8d52 | ||
|
|
9e080a5f5c | ||
|
|
7d7d4c05ab | ||
|
|
d4c7044708 | ||
|
|
52dd8c7c8b | ||
|
|
398ac8b6cf | ||
|
|
911792b214 | ||
|
|
8e54989502 | ||
|
|
4f16579063 | ||
|
|
e1f1698413 | ||
|
|
5eb999b98a | ||
|
|
5e7af835d8 | ||
|
|
f19f2c5a20 | ||
|
|
ff2aceb781 | ||
|
|
dc1410adbb | ||
|
|
49af38f3c5 | ||
|
|
09cf4dcd07 | ||
|
|
95c92285b9 | ||
|
|
81b7be9205 | ||
|
|
c308cde139 | ||
|
|
49889cd2cc | ||
|
|
d0ea71f0c9 | ||
|
|
a6f93dae1e | ||
|
|
f968999393 | ||
|
|
37348ed1f4 | ||
|
|
8602d39d7c | ||
|
|
fd41390e20 | ||
|
|
712064bced | ||
|
|
771a696de0 | ||
|
|
a56cbcc2f4 | ||
|
|
8e659a2c97 | ||
|
|
423bd0adb8 | ||
|
|
1987848f8b | ||
|
|
13ac8541f2 | ||
|
|
0e42d61ffa | ||
|
|
64b4cb1bb9 | ||
|
|
efd723deec | ||
|
|
4c2d2068a4 | ||
|
|
32529dd7e9 | ||
|
|
3ad54d5f21 | ||
|
|
6d81c37645 | ||
|
|
da1c51eb5b | ||
|
|
70c37fb9f2 | ||
|
|
5e565be35b | ||
|
|
eeb39ca228 | ||
|
|
2d5bc2bc52 | ||
|
|
4fb517c8cb | ||
|
|
de376cd9f4 | ||
|
|
d939984f08 | ||
|
|
89edcb7a38 | ||
|
|
ac16a0fb26 | ||
|
|
9189fb2082 | ||
|
|
a2e7670560 | ||
|
|
7c9f14b4d9 | ||
|
|
f05849aada | ||
|
|
66e91ee4e8 | ||
|
|
015d8de101 | ||
|
|
69e575e0ef | ||
|
|
6c84e49a7f | ||
|
|
f4e5e8fd72 | ||
|
|
fe8c738196 | ||
|
|
665dfac5ea | ||
|
|
57f9806819 | ||
|
|
7874135014 | ||
|
|
f86e7b17ef | ||
|
|
3803173745 | ||
|
|
6af30bf6df | ||
|
|
7bea6a214a | ||
|
|
9becc35fa1 | ||
|
|
7212a4cd7f | ||
|
|
c604640ab9 | ||
|
|
23fee2b7b7 | ||
|
|
c3d521960e | ||
|
|
484396cce1 | ||
|
|
ce4dec6136 | ||
|
|
67f42f518e | ||
|
|
0a0e947282 | ||
|
|
8a4127574a | ||
|
|
b29d3e5d6a | ||
|
|
daaece8d36 | ||
|
|
0e5b1aaf0e | ||
|
|
e672e49435 | ||
|
|
b9a38a486d | ||
|
|
d34c46658d | ||
|
|
e120b647ea | ||
|
|
936b8ae903 | ||
|
|
147fe2c277 | ||
|
|
84413aa490 | ||
|
|
3fea95f8c5 | ||
|
|
b839bce010 | ||
|
|
fc70c098a9 | ||
|
|
7b74d73a9a | ||
|
|
44ee3bbdfe | ||
|
|
673285baf8 | ||
|
|
2b22e327e3 | ||
|
|
95d65236f6 | ||
|
|
8971172444 | ||
|
|
6ff5836ca9 | ||
|
|
8e9a4e9601 | ||
|
|
1ce5c05622 | ||
|
|
7f83e83aed | ||
|
|
e677e7345b | ||
|
|
ae52aa2c55 | ||
|
|
b83f6e93bf | ||
|
|
3606ee6275 | ||
|
|
70571cccfe | ||
|
|
87fe02b0b4 | ||
|
|
8e48087f65 | ||
|
|
4e7c3ee2f1 | ||
|
|
60a7549662 | ||
|
|
fe7c9488d8 | ||
|
|
b7d39e55b6 | ||
|
|
805d8030b4 | ||
|
|
4c6fc5d2d2 | ||
|
|
913b8ee238 | ||
|
|
3688e4d938 | ||
|
|
03842db57f | ||
|
|
6f34157a84 | ||
|
|
86a82faa15 | ||
|
|
2e506b72a6 | ||
|
|
d2ba0a22d5 | ||
|
|
cfc3488a8c | ||
|
|
0463e82470 | ||
|
|
93b151b377 | ||
|
|
1c6fa28d87 | ||
|
|
71d01263b5 | ||
|
|
23268907bd | ||
|
|
076e3061b3 | ||
|
|
d9f8fa2ed0 | ||
|
|
c33f9a6d61 | ||
|
|
07f71f3d4d | ||
|
|
48f8a4c718 | ||
|
|
ff29b1badf | ||
|
|
05d3233c03 | ||
|
|
203b5d925b | ||
|
|
b2f6338b0b | ||
|
|
26e526d5fe | ||
|
|
054d6c58cb | ||
|
|
cc6b5ea0f5 | ||
|
|
495985ace8 | ||
|
|
a304853a7b | ||
|
|
eb2c58f0e9 | ||
|
|
125429bccd | ||
|
|
c5bf1b65ba | ||
|
|
5286a018df | ||
|
|
c408744385 | ||
|
|
5efd5f310f | ||
|
|
e35c14d8e3 | ||
|
|
d49fcc2307 | ||
|
|
afc1dfcf10 | ||
|
|
2dd06ed0c3 | ||
|
|
2ca223cd2c | ||
|
|
f6cf018625 | ||
|
|
1d7806c96e | ||
|
|
650fd892f8 | ||
|
|
83710b14b1 | ||
|
|
1eeb22f579 | ||
|
|
1d70e73106 | ||
|
|
9d93b52be1 | ||
|
|
4431772007 | ||
|
|
f17fafbb48 | ||
|
|
8d30399f33 | ||
|
|
5feb934a4a | ||
|
|
9466b96f69 | ||
|
|
ad00d1b145 | ||
|
|
89ea9ba8a0 | ||
|
|
e08fb3eeb6 | ||
|
|
5800c8910e | ||
|
|
1ec77cb0b5 | ||
|
|
3329514066 | ||
|
|
addc3f322f | ||
|
|
79dcd16d37 | ||
|
|
802c72a9aa | ||
|
|
bf96676df5 | ||
|
|
d245b93196 | ||
|
|
6f96b555e9 | ||
|
|
872700d1be | ||
|
|
1ac08ee1f2 | ||
|
|
ada433150c | ||
|
|
ee40a69e57 | ||
|
|
d6fcaffa99 | ||
|
|
fe4bdda549 | ||
|
|
6a1016a383 | ||
|
|
c53f08d0a7 | ||
|
|
2807ff30e6 | ||
|
|
cf3b9c21dd | ||
|
|
ee616412d6 | ||
|
|
341c6f8722 | ||
|
|
978df99e5c | ||
|
|
a91e2a90b4 | ||
|
|
49c54c6a58 | ||
|
|
6b72163dbe | ||
|
|
93a04c64d2 | ||
|
|
d01d814778 | ||
|
|
af15c256b9 | ||
|
|
dde0056ba2 | ||
|
|
dede5b548c | ||
|
|
7be0831e7c | ||
|
|
660275dc74 | ||
|
|
957e01fbf2 | ||
|
|
b01ffaf763 | ||
|
|
8d6d53a448 | ||
|
|
8f13bfcb4a | ||
|
|
ddf45bf7a7 | ||
|
|
9fabeac7e1 | ||
|
|
758b1f9365 | ||
|
|
0050d34d0e | ||
|
|
fd547383c4 | ||
|
|
edb640f496 | ||
|
|
4569fa3e86 | ||
|
|
33af012352 | ||
|
|
5cc6688d9c | ||
|
|
f8a5f610bb | ||
|
|
877ac8adc7 | ||
|
|
39040c263e | ||
|
|
08759762a1 | ||
|
|
7897fbcf36 | ||
|
|
ce93bb1afb | ||
|
|
3e8085f460 | ||
|
|
2ab201d278 | ||
|
|
ccdac844b9 | ||
|
|
4474939c51 | ||
|
|
f32975ccb0 | ||
|
|
4f92b22955 | ||
|
|
1d8ac0ab45 | ||
|
|
4cfc5258b2 | ||
|
|
14b7415005 | ||
|
|
f482b712ca | ||
|
|
c40a5cbaaa | ||
|
|
9d0efca1e5 | ||
|
|
cf4d76e9a4 | ||
|
|
56569509f9 | ||
|
|
a0904be305 | ||
|
|
7d156fb47b | ||
|
|
e1796d24ad | ||
|
|
2899b1f8eb | ||
|
|
335a033091 | ||
|
|
80aa2367b0 | ||
|
|
e6ca5884a3 | ||
|
|
ef52c84e25 | ||
|
|
478f2fc9ae | ||
|
|
98afededa6 | ||
|
|
7ad2ca9f16 | ||
|
|
03ab379c63 | ||
|
|
bd71efd22b | ||
|
|
87b7a1c759 | ||
|
|
5c59c3b1b8 | ||
|
|
bb98db4568 | ||
|
|
80f0afd600 | ||
|
|
5bc37b51bb | ||
|
|
0f68892d11 | ||
|
|
37e731c2fd | ||
|
|
8312ae30f1 | ||
|
|
28ec42dddf | ||
|
|
d9f572188e | ||
|
|
720a95d472 | ||
|
|
74ba60f4f1 | ||
|
|
e717473a21 | ||
|
|
aca1e26c3b | ||
|
|
0b1bcebc72 | ||
|
|
058a5dca17 | ||
|
|
da21131f9c | ||
|
|
30fd9f84e0 | ||
|
|
291f881e61 | ||
|
|
bdb465b54e | ||
|
|
e39a10da22 | ||
|
|
8f4ab6a7f1 | ||
|
|
3259fc1a52 | ||
|
|
05d85a0dc1 | ||
|
|
e7c4658805 | ||
|
|
51220bcae6 | ||
|
|
e8ff42dd75 | ||
|
|
6bd54ccf2b | ||
|
|
c3bcaba61e | ||
|
|
14693b4a6e | ||
|
|
b1a75df507 | ||
|
|
42d65cf0dd | ||
|
|
b18803e343 | ||
|
|
a36a071dbe | ||
|
|
edc95e1fa0 | ||
|
|
11b3d3ef41 | ||
|
|
64f339b1fa | ||
|
|
a6eefa7afd | ||
|
|
837d1fb43d | ||
|
|
0d190b4ade | ||
|
|
fbc7acea60 | ||
|
|
e6676e1a05 | ||
|
|
b9a56c6c65 | ||
|
|
520c152cc6 | ||
|
|
f7ddf6f872 | ||
|
|
e6997d81c1 | ||
|
|
31923fc73c | ||
|
|
5dbfc97780 | ||
|
|
da525fa1c8 | ||
|
|
372866ee95 | ||
|
|
d1e1df01c0 | ||
|
|
b12d9f352b | ||
|
|
b3dc4b617b | ||
|
|
67e3ff368d | ||
|
|
569ad41894 | ||
|
|
bd5225967d | ||
|
|
9c9df4ee3b | ||
|
|
a859a20062 | ||
|
|
4fd8e736f9 | ||
|
|
41fc9b329b | ||
|
|
3ea9408103 | ||
|
|
589170ac8a | ||
|
|
54bd6cd402 | ||
|
|
9374b56ec7 | ||
|
|
f6a3ea79ef | ||
|
|
840e2610c5 | ||
|
|
d673dbf1cc | ||
|
|
26c5de3122 | ||
|
|
7c5d2647c8 | ||
|
|
3a6256179c | ||
|
|
1d4e5fbd70 | ||
|
|
a456fffe45 | ||
|
|
3240924b2c | ||
|
|
670406991c | ||
|
|
839543bbdd | ||
|
|
47511b33a3 | ||
|
|
264fd0b3a4 | ||
|
|
f10af90c14 | ||
|
|
782a82ea30 | ||
|
|
bc0d6bb09e | ||
|
|
54fd9d9492 | ||
|
|
59f804c96a | ||
|
|
fba1325abe | ||
|
|
4b634ad67a | ||
|
|
3990ba055a | ||
|
|
48784a3d7a | ||
|
|
61e87718da | ||
|
|
a55dd34eda | ||
|
|
fc304e5dc7 | ||
|
|
3870950e29 | ||
|
|
fc97830d22 | ||
|
|
c7579c259b | ||
|
|
1272e09a73 | ||
|
|
79b4cbe7f1 | ||
|
|
9a000f7881 | ||
|
|
30367ad328 | ||
|
|
d67b447895 | ||
|
|
0d9c727dde | ||
|
|
321a56d5cf | ||
|
|
fbbc4ee864 | ||
|
|
36f6e792db | ||
|
|
f71c7c5076 | ||
|
|
33ec5a5cce | ||
|
|
bbcf0add3f | ||
|
|
80aa041d1c | ||
|
|
35617e97a5 | ||
|
|
9a802c0db9 | ||
|
|
d588f2fdee | ||
|
|
ad0424175d | ||
|
|
3bf9856c26 | ||
|
|
2395ee05f9 | ||
|
|
2a5168e800 | ||
|
|
da861cc73c | ||
|
|
4cd974cb0e | ||
|
|
14cb32fc58 | ||
|
|
3197671950 | ||
|
|
cd29baf9cc | ||
|
|
03ca76e679 | ||
|
|
933855baac | ||
|
|
ee61259114 | ||
|
|
d26f88e0f1 | ||
|
|
c8fc144b8a | ||
|
|
d8ae647140 | ||
|
|
ab5767bee8 | ||
|
|
7c63d3c176 | ||
|
|
d837dfdd77 | ||
|
|
31612a7cb1 | ||
|
|
3d25ec32c0 | ||
|
|
e3cda07f0f | ||
|
|
18762961e1 | ||
|
|
9cc1112a3e | ||
|
|
2204a8437f | ||
|
|
c37e0d2fab | ||
|
|
4ce85692a7 | ||
|
|
860658f367 | ||
|
|
4310ce95ea | ||
|
|
66ae6b2a33 | ||
|
|
5f45a38f08 | ||
|
|
c26cd30dfd | ||
|
|
4b0fe94f34 | ||
|
|
7fc80f8e10 | ||
|
|
2875676308 | ||
|
|
814726d545 | ||
|
|
0ae60a0463 | ||
|
|
c96bef9ac5 | ||
|
|
db9fd9e4d5 | ||
|
|
90ab2f0dbd | ||
|
|
f8f6cb3cc2 | ||
|
|
72d741f795 | ||
|
|
e784f4bcf0 | ||
|
|
a50f34d2b4 | ||
|
|
dce58a7793 | ||
|
|
5c9de8de7c | ||
|
|
c1c2ceb33e | ||
|
|
b39eca0e0e | ||
|
|
cadcc69eef | ||
|
|
120f2b2d44 | ||
|
|
495d7ecf19 | ||
|
|
140a45a7cb | ||
|
|
bd12ff73f8 | ||
|
|
7b4182dce3 | ||
|
|
f8098423a3 | ||
|
|
9e1a8c29ac | ||
|
|
157833d806 | ||
|
|
91a61eb014 | ||
|
|
1f037dbda7 | ||
|
|
d9c68e7360 | ||
|
|
51507d3905 | ||
|
|
31e83ebbb5 | ||
|
|
3e2f1fb5a8 | ||
|
|
5ad360eaf2 | ||
|
|
b7b8cd3fea | ||
|
|
243fccac9e | ||
|
|
51b3035434 | ||
|
|
be9fba23ac | ||
|
|
0ac9b703d5 | ||
|
|
21c51ff074 | ||
|
|
a735b2f0b0 | ||
|
|
0baea2c5ea | ||
|
|
ca2463567e | ||
|
|
c950dc5f53 | ||
|
|
5c6a29f2d7 | ||
|
|
6e12a9d22e | ||
|
|
df819853ef | ||
|
|
d8c3fb4190 | ||
|
|
25e5ecfe61 | ||
|
|
809b5bbe18 | ||
|
|
1ec9139307 | ||
|
|
293ea1f6cf | ||
|
|
ed43a47ad7 | ||
|
|
82980639c8 | ||
|
|
01dd493475 | ||
|
|
0b29c8c51b | ||
|
|
7c2f4277c3 | ||
|
|
8c515f542c | ||
|
|
73d80b136f | ||
|
|
93c5cf70a0 | ||
|
|
e2a098c3ff | ||
|
|
c766048224 | ||
|
|
9eb87fae62 | ||
|
|
7f2f4d8e3b | ||
|
|
ce5f9c0f08 | ||
|
|
cf443ed217 | ||
|
|
bde582d8ff | ||
|
|
0a00332580 | ||
|
|
137d7bf240 | ||
|
|
302c7ef455 | ||
|
|
87b54a44c0 | ||
|
|
f9eee0de93 | ||
|
|
1114d2d30d | ||
|
|
65d50d046c | ||
|
|
b2ef957682 | ||
|
|
56be23e304 | ||
|
|
215869037b | ||
|
|
f5d4581911 | ||
|
|
5848efdf67 | ||
|
|
c79277afaf | ||
|
|
59c8e1ae1c | ||
|
|
d50678649a | ||
|
|
c19f440094 | ||
|
|
b60160f2b9 | ||
|
|
e54c4ed435 | ||
|
|
749774cd03 | ||
|
|
7cc609fdc3 | ||
|
|
2cc8bed203 | ||
|
|
175f02a1c3 | ||
|
|
8a8ac0ccde | ||
|
|
168427f24e | ||
|
|
8338518405 | ||
|
|
95783b7ff3 | ||
|
|
f7a4f956d7 | ||
|
|
feea4d4785 | ||
|
|
e267623025 | ||
|
|
5c22583a52 | ||
|
|
7ba93c9742 | ||
|
|
b4b40bf82f | ||
|
|
b96218f305 | ||
|
|
e4013a616f | ||
|
|
6f3ab8f974 | ||
|
|
c47fc61b73 | ||
|
|
01042bbc54 | ||
|
|
e75f8b75c5 | ||
|
|
ae5e77925a | ||
|
|
b1f2a0e057 | ||
|
|
6f61d5aa11 | ||
|
|
9562f06fea | ||
|
|
c6eaf4ae03 | ||
|
|
24c994dfb5 | ||
|
|
f623e5d004 | ||
|
|
9ac03a00d8 | ||
|
|
52b819a1b7 | ||
|
|
3ad32e0fc7 | ||
|
|
18c4a55518 | ||
|
|
0d8a889556 | ||
|
|
25f180a297 | ||
|
|
ae2f2d8294 | ||
|
|
d3ac95d1f6 | ||
|
|
a138c4a1c7 | ||
|
|
0f359e84d6 | ||
|
|
49d73b7d4f | ||
|
|
33dc7b82ea | ||
|
|
4714ea6037 | ||
|
|
c2e5f241b0 | ||
|
|
7f0e299b74 | ||
|
|
e418ccf8e0 | ||
|
|
53e119c0ce | ||
|
|
f934d3b7b1 | ||
|
|
12d5b9f6f6 | ||
|
|
727a5cfbf5 | ||
|
|
6345080769 | ||
|
|
928f3d32cd | ||
|
|
81fd2dc82f | ||
|
|
225918ac52 | ||
|
|
796343bc5e | ||
|
|
cdb86f6c11 | ||
|
|
625e26f01f | ||
|
|
21f0429fd6 | ||
|
|
e7a30048cf | ||
|
|
ed952e19eb | ||
|
|
e4a7bdd0eb | ||
|
|
13e394bbfe | ||
|
|
a6b95064d9 | ||
|
|
c30a556bdc | ||
|
|
2bebba740e | ||
|
|
57dfb02689 | ||
|
|
7931d8b26e | ||
|
|
434d50b2d1 | ||
|
|
fad905a426 | ||
|
|
475c3b690b | ||
|
|
88eae3cb9a | ||
|
|
0021c7233c | ||
|
|
725224b6c7 | ||
|
|
28f399079d | ||
|
|
3e0d594e29 | ||
|
|
9092b1f9b0 | ||
|
|
4ac30ff7b2 | ||
|
|
803d3395d6 | ||
|
|
51288231cd | ||
|
|
a44cd528ef | ||
|
|
cfbe0b031e | ||
|
|
bf7fb1620e | ||
|
|
50056fd301 | ||
|
|
927b134996 | ||
|
|
9d66619cda | ||
|
|
c9670536cc | ||
|
|
731518a4fc | ||
|
|
764df316a7 | ||
|
|
1ecfdafbe8 | ||
|
|
4b09aeefd6 | ||
|
|
71e407c3a5 | ||
|
|
48c4e7432d | ||
|
|
202194c16f | ||
|
|
0af5713483 | ||
|
|
3b6a5c353c | ||
|
|
e6d8f34db0 | ||
|
|
ef97a0d184 | ||
|
|
40866754d1 | ||
|
|
d5fb7d094c | ||
|
|
48b861054c | ||
|
|
568b7541a4 | ||
|
|
914a6e6a57 | ||
|
|
d1e737dc96 | ||
|
|
ae68a05f87 | ||
|
|
79e6f3ef1c | ||
|
|
5f137f4055 | ||
|
|
4f5215a312 | ||
|
|
06ea8f6cb1 | ||
|
|
2e2890d8d3 | ||
|
|
a07ef6c273 | ||
|
|
a02acc9c67 | ||
|
|
bc5e61b04b | ||
|
|
665036189d | ||
|
|
8fe72e7f2e | ||
|
|
4a373015de | ||
|
|
31ad540286 | ||
|
|
448faebfad | ||
|
|
3b7f25071e | ||
|
|
c661395bb0 | ||
|
|
c231220b42 | ||
|
|
1c6e5bec73 | ||
|
|
8919aaad94 | ||
|
|
ff6a2fe734 | ||
|
|
5ca4f7e411 | ||
|
|
2c105718b0 | ||
|
|
9cc471f66f | ||
|
|
e18867c938 | ||
|
|
27bd942c4c | ||
|
|
234ca72e73 | ||
|
|
f35a09a34d | ||
|
|
fa6d06a4ac | ||
|
|
ec6daeeadb | ||
|
|
f24f25f071 | ||
|
|
dbf0e3af9f | ||
|
|
6ecb607c0b | ||
|
|
1bf6b00be0 | ||
|
|
4b4f52e39a | ||
|
|
1170efbe02 | ||
|
|
3e86e73fed | ||
|
|
dd43861572 | ||
|
|
61a19ca9e0 | ||
|
|
d02af31e7c | ||
|
|
ab7b88b67b | ||
|
|
b80c816c5a | ||
|
|
c0ad6f49e7 | ||
|
|
91a56a8c2c | ||
|
|
135a473dc5 | ||
|
|
a8b53b07e4 | ||
|
|
171cbd4497 | ||
|
|
a1383612bc | ||
|
|
fe798c19f3 | ||
|
|
ead3add4cc | ||
|
|
db13c2d1c5 | ||
|
|
f2daab4b80 | ||
|
|
6fd02c83ab | ||
|
|
c288a0503a | ||
|
|
2e2b65a4bb | ||
|
|
982d0a53f9 | ||
|
|
f89ecfb595 | ||
|
|
bddb96a5fa | ||
|
|
39b29d12e4 | ||
|
|
6cb1e2c06d | ||
|
|
d932a9a862 | ||
|
|
341b552d0b | ||
|
|
1586bd9768 | ||
|
|
e8dde3a756 | ||
|
|
bb591bf56d | ||
|
|
8cf83b276a | ||
|
|
a1547a7288 | ||
|
|
0722403a23 | ||
|
|
cd4a91d975 | ||
|
|
c7870b4e9d | ||
|
|
239dae5a65 | ||
|
|
13c89d18d7 | ||
|
|
c5dc42eafe | ||
|
|
59b3faab1e | ||
|
|
b88beb5002 | ||
|
|
9fea35c25d | ||
|
|
372013bcc6 | ||
|
|
20ca58b570 | ||
|
|
9c4cca20fb | ||
|
|
6b8536a86e | ||
|
|
3b41857637 | ||
|
|
fa78ac2157 | ||
|
|
0a0bb9c83c | ||
|
|
36834a039e | ||
|
|
4960c898bb | ||
|
|
af61edbc5c | ||
|
|
237bfc08a1 | ||
|
|
032e9a089d | ||
|
|
ba83779dde | ||
|
|
267d72f844 | ||
|
|
b533cdb89f | ||
|
|
3f118ac76b | ||
|
|
08a8f0097c | ||
|
|
c7a3599e46 | ||
|
|
62f80baa69 | ||
|
|
09dc8cd437 | ||
|
|
c613ba401b | ||
|
|
866df0b5cf | ||
|
|
8f2b56ba05 | ||
|
|
2049885f11 | ||
|
|
4caef337d5 | ||
|
|
a644792bf6 | ||
|
|
54c7f8fc6a | ||
|
|
2e59b87bbe | ||
|
|
f89242ad8f | ||
|
|
845903788c | ||
|
|
53a9047c20 | ||
|
|
a2b74bbc30 | ||
|
|
50ef93cc45 | ||
|
|
e2a5da9c14 | ||
|
|
bf31116d0d | ||
|
|
d79fa8c4f3 | ||
|
|
87e0a7bea3 | ||
|
|
b34cabd55c | ||
|
|
704bd36c77 | ||
|
|
5c2c4d0f4b | ||
|
|
80cca5cb34 | ||
|
|
dcd603f2f5 | ||
|
|
1bee3b49bf | ||
|
|
11bcccb8f9 | ||
|
|
7425493706 | ||
|
|
dca0cfd424 | ||
|
|
21314def89 | ||
|
|
90e544d279 | ||
|
|
89e21558a1 | ||
|
|
b28cc51457 |
No files matched your search
@@ -0,0 +1,7 @@
|
||||
* text=auto
|
||||
|
||||
*.sh text eol=lf
|
||||
gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
|
||||
*.jar binary
|
||||
@@ -1,12 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://paypal.me/neznamy1'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Server version**
|
||||
|
||||
|
||||
**Plugin version** ("latest" is not a version)
|
||||
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
|
||||
**How to reproduce**
|
||||
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
|
||||
**Actual behavior**
|
||||
|
||||
|
||||
**Additional info** (errors, screenshots, ...)
|
||||
@@ -0,0 +1,65 @@
|
||||
name: Bug Report
|
||||
description: Plugin does not work as described.
|
||||
labels: ["Type: Bug"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: How the plugin behaves
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: How the plugin is supposed to behave instead with your configuration.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: What to do in order to reproduce this issue
|
||||
placeholder: |
|
||||
Step 1: ..
|
||||
Step 2: ..
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Anything else you want to provide that will help in resolving the issue faster, such as screenshots or videos.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have read the wiki to make sure it's not an issue with configuration
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
|
||||
required: true
|
||||
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
|
||||
required: true
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Compatibility issue
|
||||
description: A compatibility issue with another plugin/mod or a specific version of a platform marked as supported
|
||||
labels: ["Type: Compatibility issue"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 4.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Software causing compatibility issue
|
||||
description: plugin/mod/platform version causing the issue
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Anything else you want to provide that will help in resolving the issue faster.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: The compatibility wiki page does not mention this incompatibility
|
||||
required: true
|
||||
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
|
||||
required: true
|
||||
@@ -0,0 +1,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
|
||||
@@ -0,0 +1,57 @@
|
||||
name: Error
|
||||
description: Error message with a stack trace in console or error file.
|
||||
labels: ["Type: Error"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Stack trace
|
||||
description: The error message and stack trace you received in console or errors.log file.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce (if known)
|
||||
description: What to do in order to reproduce this issue
|
||||
placeholder: |
|
||||
Step 1: ..
|
||||
Step 2: ..
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Any other info you want to provide.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have included a paste of the error
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
|
||||
required: true
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Memory leak
|
||||
description: Memory leak caused by TAB.
|
||||
labels: ["Type: Memory leak"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Heap dump
|
||||
description: Link to uploaded memory dump which I can look into.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Uncleared object(s)
|
||||
description: Objects you believe should have been removed from the memory but are not
|
||||
placeholder: |
|
||||
There is only 1 player online, but heap dump has 100 instances of TabPlayer
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Any other info you want to provide.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have included download link of my heap dump
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: I have explained what I believe is wrong with the heap dump
|
||||
required: true
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Performance issue
|
||||
description: Performance issue in TAB or in server caused by TAB
|
||||
labels: ["Type: Performance"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Evidence
|
||||
description: Any evidence proving the performance issue, such as output of /tab cpu or timings.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce (if known)
|
||||
description: What to do in order to reproduce this issue
|
||||
placeholder: |
|
||||
Step 1: ..
|
||||
Step 2: ..
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Any other info you want to provide.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have included sufficient evidence of the issue
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Wiki change
|
||||
description: Change to text on a wiki page or adding a new section.
|
||||
labels: ["Type: Wiki change"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Page link
|
||||
description: Link to the page that should receive the change.
|
||||
placeholder: https://github.com/NEZNAMY/TAB/wiki/Installation
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current text (if applicable)
|
||||
description: Current version of the text (if modifying), ignore in case of new section.
|
||||
placeholder: |
|
||||
Installation on Bukkit/Spigot/Paper is as simple as it can be. Just put the plugin into plugins folder and restart the server. You can also run `/plugman load tab` to load the plugin while server is running.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: New text
|
||||
description: What the text should be changed to / how should new section look
|
||||
placeholder: |
|
||||
Put the plugin into plugins folder and restart the server. In case you don't want to restart your server, loading with plugins like PlugMan is supported.
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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 21
|
||||
uses: actions/setup-java@v4 #https://github.com/actions/setup-java/releases
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 21
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: "${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}"
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Build and analyze
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
run: ./gradlew clean build
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
|
||||
with:
|
||||
name: Click here to download
|
||||
path: jar/build/libs/TAB-**.jar
|
||||
+6
-1
@@ -33,5 +33,10 @@ hs_err_pid*
|
||||
.idea/
|
||||
|
||||
# other
|
||||
.gitattributes
|
||||
*.iml
|
||||
run/
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -0,0 +1,9 @@
|
||||
If this plugin has helped you in any way, and you would like to return something back
|
||||
to make the plugin even better, there is a lot of ways to contribute:
|
||||
* Open a **pull request** containing a new feature or a bug fix, which you believe many users will benefit from.
|
||||
* Make detailed high-quality **bug reports**. The difference between a bug getting fixed in 1 week vs 1 hour
|
||||
is in quality of the report (typically providing correct steps to reproduce that actually work).
|
||||
* Test **experimental builds**, which can be found in [GitHub Actions](https://github.com/NEZNAMY/TAB/actions) for problems of any kind including bugs.
|
||||
This way you will help make the actual releases more stable / help get stable releases sooner.
|
||||
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
|
||||
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples.
|
||||
@@ -1,4 +1,4 @@
|
||||
Apache License
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
@@ -172,30 +172,3 @@ Apache License
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,46 +1,58 @@
|
||||
## Introduction
|
||||
Here it is. After countless requests to go opensource, I did it to make those people quiet. It worked.
|
||||
It's already been a year and I still can't see any positives of it. This way I just made it easier for everyone to make a plugin as good, so people can stop using it and switch to something better. Or just get premium version for free.
|
||||
|
||||
|
||||
## Compiling
|
||||
Plugin can be compiled using maven. Once you've purchased the premium version, you can enable premium features by setting [this line to return true](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/TAB.java#L78) and apply your code improvements. Enabling it without purchasing the plugin may result in the features not working correctly.
|
||||
**No support is provided for self-compiled jars, only for official compilations.** If you compiled it yourself, it is highly likely you modified something, which might be the reason why is the plugin not working. If you didn't modify anything, you can just get the plugin from official download places.
|
||||
|
||||
|
||||
## Links
|
||||
Free version
|
||||
Spigot (releases): https://www.spigotmc.org/resources/57806/
|
||||
Pre-releases https://github.com/NEZNAMY/TAB/releases
|
||||
Premium version
|
||||
Polymart (releases): https://polymart.org/resource/484
|
||||
MC-Market (releases): https://www.mc-market.org/resources/14009/
|
||||
Pre-releases on discord for verified buyers
|
||||
Bukkit bridge https://www.spigotmc.org/resources/83966/
|
||||
Wiki https://github.com/NEZNAMY/TAB/wiki
|
||||
Discord invite link: https://discord.gg/EaSvdk6
|
||||
|
||||
|
||||
## Downloads
|
||||
Official release: https://www.spigotmc.org/resources/57806/
|
||||
Pre-releases: https://github.com/NEZNAMY/TAB/releases
|
||||
|
||||
|
||||
## Maven Repository
|
||||
```
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
# About
|
||||
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
|
||||
similar plugins in terms of features, performance and compatibility.
|
||||
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
|
||||
|
||||
<dependencies>
|
||||
<!-- TAB -->
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>2.8.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
# 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 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.
|
||||
|
||||
# Documentation
|
||||
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
|
||||
of all features, as well as information regarding compatibility or limitations of each feature.
|
||||
|
||||
# Issues
|
||||
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is an issue tracker for all the kind of problems covered in issue templates.
|
||||
Please refrain from attempting to use it to receive help with your server, such issues will be closed immediately.
|
||||
When opening an issue, provide as much information as possible. The difference between a bug getting fixed in 1 week vs 1 hour
|
||||
is in quality of the report. Bug reports where steps to reproduce are "idk" have no value and will not help to get the bug fixed at all.
|
||||
|
||||
# Contributing
|
||||
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
|
||||
|
||||
# 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.
|
||||
@@ -0,0 +1,2 @@
|
||||
# Security Policy
|
||||
If you found a security issue, send me a private message anywhere.
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies {
|
||||
compileOnlyApi("org.jetbrains:annotations:24.1.0")
|
||||
}
|
||||
|
||||
tasks.javadoc {
|
||||
enabled = project.hasProperty("enable-javadoc")
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.api.bossbar.BossBarManager;
|
||||
import me.neznamy.tab.api.event.EventBus;
|
||||
import me.neznamy.tab.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
|
||||
import me.neznamy.tab.api.tablist.SortingManager;
|
||||
import me.neznamy.tab.api.tablist.HeaderFooterManager;
|
||||
import me.neznamy.tab.api.tablist.TabListFormatManager;
|
||||
import me.neznamy.tab.api.nametag.NameTagManager;
|
||||
import me.neznamy.tab.api.tablist.layout.LayoutManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The primary API class to get instances of other API classes
|
||||
*/
|
||||
public abstract class TabAPI {
|
||||
|
||||
/** Instance of the API */
|
||||
@Setter private static TabAPI instance;
|
||||
|
||||
/**
|
||||
* Returns API instance. If instance was not set by the plugin, throws
|
||||
* {@code IllegalStateException}. This is usually caused by shading the API
|
||||
* into own project, which is not allowed. Another option is calling the method
|
||||
* before plugin was able to load.
|
||||
*
|
||||
* @return API instance
|
||||
* @throws IllegalStateException
|
||||
* If instance is {@code null}
|
||||
*/
|
||||
public static @NotNull TabAPI getInstance() {
|
||||
if (instance == null) throw new IllegalStateException("The API instance is null. This can have 2 possible causes: \n" +
|
||||
"#1 - API was called before TAB was loaded. This means your plugin was loaded before TAB was. To make sure your " +
|
||||
"plugin loads after TAB, add it as a depend or soft depend of your plugin.\n" +
|
||||
"#2 - You shaded TAB's classes into your plugin, instead of only using them. This is not allowed. To verify this " +
|
||||
"is your case, unzip your plugin and check for TAB's classes. If they are there, you will need to fix your compiler " +
|
||||
"to not include them, such as scope provided for maven compilation.");
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player object from given UUID
|
||||
*
|
||||
* @param id
|
||||
* Player UUID
|
||||
* @return player object from given UUID
|
||||
*/
|
||||
public abstract @Nullable TabPlayer getPlayer(@NonNull UUID id);
|
||||
|
||||
/**
|
||||
* Returns player object from given name
|
||||
*
|
||||
* @param name
|
||||
* Player name
|
||||
* @return player object from given name
|
||||
*/
|
||||
public abstract @Nullable TabPlayer getPlayer(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Returns collection of all online players. Will return empty list if plugin is disabled (due to a broken configuration file for example).
|
||||
*
|
||||
* @return collection of online players
|
||||
*/
|
||||
public abstract @NotNull TabPlayer[] getOnlinePlayers();
|
||||
|
||||
/**
|
||||
* Return BossBar manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return BossBar manager
|
||||
*/
|
||||
public abstract @Nullable BossBarManager getBossBarManager();
|
||||
|
||||
/**
|
||||
* Returns scoreboard manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return scoreboard manager
|
||||
*/
|
||||
public abstract @Nullable ScoreboardManager getScoreboardManager();
|
||||
|
||||
/**
|
||||
* Returns team manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return team manager
|
||||
*/
|
||||
public abstract @Nullable NameTagManager getNameTagManager();
|
||||
|
||||
/**
|
||||
* Returns header/footer manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return Header/footer manager
|
||||
*/
|
||||
public abstract @Nullable HeaderFooterManager getHeaderFooterManager();
|
||||
|
||||
/**
|
||||
* Returns PlaceholderManager instance
|
||||
*
|
||||
* @return PlaceholderManager instance
|
||||
*/
|
||||
public abstract @NotNull PlaceholderManager getPlaceholderManager();
|
||||
|
||||
/**
|
||||
* Returns TabList name format manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return TabList name format manager
|
||||
*/
|
||||
public abstract @Nullable TabListFormatManager getTabListFormatManager();
|
||||
|
||||
/**
|
||||
* Returns Layout manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return Layout manager
|
||||
*/
|
||||
public abstract @Nullable LayoutManager getLayoutManager();
|
||||
|
||||
/**
|
||||
* Returns Sorting manager instance if at least one feature capable of sorting players is enabled.
|
||||
* If not, returns {@code null}.
|
||||
*
|
||||
* @return Sorting manager
|
||||
*/
|
||||
public abstract @Nullable SortingManager getSortingManager();
|
||||
|
||||
/**
|
||||
* Gets the event bus for registering listeners for TAB events.
|
||||
*
|
||||
* @return the event bus
|
||||
*/
|
||||
public abstract @Nullable EventBus getEventBus();
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* An interface representing a player
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface TabPlayer {
|
||||
|
||||
/**
|
||||
* Returns player's name
|
||||
*
|
||||
* @return Player's name
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns player's UUID
|
||||
*
|
||||
* @return Player's UUID
|
||||
*/
|
||||
@NotNull UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Returns platform-specific entity
|
||||
*
|
||||
* @return platform's player object
|
||||
*/
|
||||
@NotNull Object getPlayer();
|
||||
|
||||
/**
|
||||
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
|
||||
*
|
||||
* @return {@code true} if player is fully loaded, {@code false} otherwise
|
||||
*/
|
||||
boolean isLoaded();
|
||||
|
||||
/**
|
||||
* Returns player's primary permission group. If group has been changed using
|
||||
* {@link #setTemporaryGroup(String)}, returns that value. Otherwise, returns group
|
||||
* detected by standard group assign logic
|
||||
*
|
||||
* @return player's primary permission group
|
||||
*/
|
||||
@NotNull String getGroup();
|
||||
|
||||
/**
|
||||
* Temporarily overrides player's group and applies all changes coming from new group.
|
||||
* This includes all properties and sorting, if used.
|
||||
* Set to {@code null} to reset back.
|
||||
*
|
||||
* @param group
|
||||
* New group to use
|
||||
* @see #hasTemporaryGroup()
|
||||
*/
|
||||
void setTemporaryGroup(@Nullable String group);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a temporary group was applied to the player using {@link #setTemporaryGroup(String)}.
|
||||
* If no group was set, returns {@code false}
|
||||
*
|
||||
* @return {@code true} if group is set, {@code false} if not
|
||||
* @see #setTemporaryGroup(String)
|
||||
*/
|
||||
boolean hasTemporaryGroup();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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 color */
|
||||
PINK,
|
||||
|
||||
/** Blue color */
|
||||
BLUE,
|
||||
|
||||
/** Red color */
|
||||
RED,
|
||||
|
||||
/** Green color */
|
||||
GREEN,
|
||||
|
||||
/** Yellow color */
|
||||
YELLOW,
|
||||
|
||||
/** Purple color */
|
||||
PURPLE,
|
||||
|
||||
/** White color */
|
||||
WHITE
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
/**
|
||||
* An enum representing all available BossBar styles.
|
||||
* Calling ordinal() will return style's network ID.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public enum BarStyle {
|
||||
|
||||
/** Fully solid line */
|
||||
PROGRESS,
|
||||
|
||||
/** 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
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An interface representing a BossBar line.
|
||||
* <p>
|
||||
* For clients 1.8 and lower it uses wither, which only allows 1 bar
|
||||
* to be displayed at a time. Entity packets are not available on BungeeCord,
|
||||
* where nothing will be displayed for these players.
|
||||
* <p>
|
||||
* For 1.9+ it uses the new BossBar feature, allowing configurable styles
|
||||
* and colors, as well as display several bars at once. Limit of maximum
|
||||
* displayed BossBars depends on client resolution and GUI scale, up to
|
||||
* 9 concurrent BossBars displayed at once.
|
||||
*/
|
||||
public interface BossBar {
|
||||
|
||||
/**
|
||||
* Returns name of this BossBar. If it was defined in config, returns
|
||||
* the name specified in config. If it was made using the API, returns
|
||||
* randomly generated ID given to this BossBar when creating.
|
||||
*
|
||||
* @return name of BossBar
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns randomly generated uuid of this BossBar used internally to match
|
||||
* the BossBar in client with packets or with Bukkit API BossBar instances.
|
||||
*
|
||||
* @return uuid of this BossBar
|
||||
*/
|
||||
@NotNull UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Changes BossBar title to specified string, supporting placeholders.
|
||||
* <p>
|
||||
* If title contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified title is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param title
|
||||
* New title to display in this BossBar
|
||||
*/
|
||||
void setTitle(@NonNull String title);
|
||||
|
||||
/**
|
||||
* Changes BossBar progress to specified string, supporting placeholders.
|
||||
* The string must be a string version of a float value, or a placeholder that
|
||||
* outputs a float value with range 0-100.
|
||||
* <p>
|
||||
* If progress contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified progress is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param progress
|
||||
* New progress to use in this BossBar
|
||||
*/
|
||||
void setProgress(@NonNull String progress);
|
||||
|
||||
/**
|
||||
* Changes BossBar progress to specified value ranging from 0 to 100.
|
||||
* <p>
|
||||
* If specified progress is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param progress
|
||||
* New progress to use in this BossBar
|
||||
*/
|
||||
void setProgress(float progress);
|
||||
|
||||
/**
|
||||
* Changes BossBar color to specified string, supporting placeholders.
|
||||
* The string must be a string version of one of the supported values,
|
||||
* or a placeholder that outputs one of them.
|
||||
* <p>
|
||||
* If color contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified color is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param color
|
||||
* New color to use in this BossBar
|
||||
*/
|
||||
void setColor(@NonNull String color);
|
||||
|
||||
/**
|
||||
* Changes BossBar color to specified enum constant.
|
||||
* <p>
|
||||
* If specified color is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param color
|
||||
* New color to use in this BossBar
|
||||
*/
|
||||
void setColor(@NonNull BarColor color);
|
||||
|
||||
/**
|
||||
* Changes BossBar style to specified string, supporting placeholders.
|
||||
* The string must be a string version of one of the supported values,
|
||||
* or a placeholder that outputs one of them.
|
||||
* <p>
|
||||
* If style contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified style is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param style
|
||||
* New style to use in this BossBar
|
||||
*/
|
||||
void setStyle(@NonNull String style);
|
||||
|
||||
/**
|
||||
* Changes BossBar style to specified enum constant.
|
||||
* <p>
|
||||
* If specified style is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param style
|
||||
* New style to use in this BossBar
|
||||
*/
|
||||
void setStyle(@NonNull BarStyle style);
|
||||
|
||||
/**
|
||||
* Returns current title of the BossBar in raw format. If it contains placeholders,
|
||||
* their raw identifiers are used in the result.
|
||||
*
|
||||
* @return title of the BossBar
|
||||
*/
|
||||
@NotNull String getTitle();
|
||||
|
||||
/**
|
||||
* Returns progress of the BossBar as a string, which is either entered string
|
||||
* containing placeholders or entered number converted to string
|
||||
*
|
||||
* @return entered progress as a string
|
||||
*/
|
||||
@NotNull String getProgress();
|
||||
|
||||
/**
|
||||
* Returns color of the BossBar as a string, which is either entered string
|
||||
* containing placeholders or entered enum value converted to string
|
||||
*
|
||||
* @return entered color as a string
|
||||
*/
|
||||
@NotNull String getColor();
|
||||
|
||||
/**
|
||||
* Returns style of the BossBar as a string, which is either entered string
|
||||
* containing placeholders or entered enum value converted to string
|
||||
*
|
||||
* @return entered style as a string
|
||||
*/
|
||||
@NotNull String getStyle();
|
||||
|
||||
/**
|
||||
* Registers this BossBar to specified player.
|
||||
* <p>
|
||||
* If the player already sees this BossBar, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* Player to register this BossBar to
|
||||
*/
|
||||
void addPlayer(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Unregisters this BossBar from specified player.
|
||||
* <p>
|
||||
* If the player does not see this BossBar, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* Player to unregister this BossBar from
|
||||
*/
|
||||
void removePlayer(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns list of players who can see this BossBar.
|
||||
* <p>
|
||||
* The returned list can only be used to read. Writing
|
||||
* will not work properly. For adding/removing players see {@link #addPlayer(TabPlayer)}
|
||||
* and {@link #removePlayer(TabPlayer)}.
|
||||
*
|
||||
* @return List of players seeing this BossBar
|
||||
*/
|
||||
@NotNull List<TabPlayer> getPlayers();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this bossbar is visible to specified player based on
|
||||
* display condition or adding via {@link #addPlayer(TabPlayer)}, {@code false} if not.
|
||||
* <p>
|
||||
* This may return {@code true} even if player cannot physically see the bossbar due to
|
||||
* having it disabled via toggle command or {@link BossBarManager#toggleBossBar(TabPlayer, boolean)}
|
||||
* (or {@link BossBarManager#setBossBarVisible(TabPlayer, boolean, boolean)}).
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if player is allowed to see this Boss Bar, {@code false} if not.
|
||||
*/
|
||||
boolean containsPlayer(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this boss bar is configured as announcement-only bar,
|
||||
* {@code false} if not.
|
||||
*
|
||||
* @return {@code true} if bar is announcement only, {@code false} if not
|
||||
*/
|
||||
boolean isAnnouncementBar();
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface allowing work with BossBars such as creating, sending
|
||||
* and toggling.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getBossBarManager()}.
|
||||
* This requires the BossBar feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface BossBarManager {
|
||||
|
||||
/**
|
||||
* Creates BossBar with specified parameters and registers it to BossBar manager,
|
||||
* allowing to retrieve it later.
|
||||
*
|
||||
* @param title
|
||||
* Title to display, supports placeholders
|
||||
* @param progress
|
||||
* Progress to use. Must be between 0 and 100
|
||||
* @param color
|
||||
* Color to use
|
||||
* @param style
|
||||
* Style to use
|
||||
* @return The newly created BossBar
|
||||
*/
|
||||
@NotNull BossBar createBossBar(@NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style);
|
||||
|
||||
/**
|
||||
* Creates BossBar with specified parameters and registers it to BossBar manager,
|
||||
* allowing to retrieve it later.
|
||||
*
|
||||
* @param title
|
||||
* Title to display, supports placeholders
|
||||
* @param progress
|
||||
* Progress to use. Must be a string version of a number, or
|
||||
* a placeholder that returns a number
|
||||
* @param color
|
||||
* Color to use. Must be a string version of one of enum constants, or
|
||||
* a placeholder that returns one of them
|
||||
* @param style
|
||||
* Style to use. Must be a string version of one of enum constants, or
|
||||
* a placeholder that returns one of them
|
||||
* @return The newly created BossBar
|
||||
*/
|
||||
@NotNull BossBar createBossBar(@NonNull String title, @NonNull String progress, @NonNull String color, @NonNull String style);
|
||||
|
||||
/**
|
||||
* Returns registered BossBar with given name. For BossBars from config it is the name
|
||||
* they were created with, for API BossBars their randomly generated name, which can be
|
||||
* retrieved using {@link me.neznamy.tab.api.bossbar.BossBar#getName()}.
|
||||
* Returns {@code null} if no such BossBar exists.
|
||||
*
|
||||
* @param name
|
||||
* Name of registered BossBar
|
||||
* @return BossBar with specified name
|
||||
*/
|
||||
@Nullable BossBar getBossBar(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Returns a map of all registered BossBars. This includes both BossBars from config
|
||||
* and those registered via API.
|
||||
* <p>
|
||||
* Map key is BossBar's name available in {@link me.neznamy.tab.api.bossbar.BossBar#getName()},
|
||||
* map value is the BossBar interface.
|
||||
*
|
||||
* @return all registered BossBars
|
||||
*/
|
||||
@NotNull Map<String, BossBar> getRegisteredBossBars();
|
||||
|
||||
/**
|
||||
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
|
||||
*
|
||||
* @param player
|
||||
* Player to toggle BossBar for
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if toggle message should be sent, {@code false} if not
|
||||
* @see #hasBossBarVisible(TabPlayer)
|
||||
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
void toggleBossBar(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns true if player can see BossBars, false if toggled via command or API.
|
||||
*
|
||||
* @param player
|
||||
* Player to check toggle status of
|
||||
* @return {@code true} if visible, {@code false} if disabled (toggled)
|
||||
* @see #toggleBossBar(TabPlayer, boolean)
|
||||
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
boolean hasBossBarVisible(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sets BossBar visibility of player to set value. If value did not change, nothing happens.
|
||||
* If visibility changed, toggle message is sent if {@code sendToggleMessage} is {@code true}.
|
||||
*
|
||||
* @param player
|
||||
* Player to set BossBar visibility of
|
||||
* @param visible
|
||||
* {@code true} if BossBar should be visible, {@code false} if not
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if toggle message should be sent if value changed, {@code false} if not
|
||||
* @see #toggleBossBar(TabPlayer, boolean)
|
||||
* @see #hasBossBarVisible(TabPlayer)
|
||||
*/
|
||||
void setBossBarVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Temporarily displays registered BossBar to player for specified amount of time in milliseconds.
|
||||
*
|
||||
* @param player
|
||||
* Player to show BossBar to
|
||||
* @param bossBar
|
||||
* Name of registered BossBar to show
|
||||
* @param duration
|
||||
* In milliseconds for how long should BossBar be displayed
|
||||
* @throws IllegalArgumentException
|
||||
* if no BossBar with specified name exists
|
||||
* @see #announceBossBar(String, int)
|
||||
* @see #getAnnouncedBossBars()
|
||||
*/
|
||||
void sendBossBarTemporarily(@NonNull TabPlayer player, @NonNull String bossBar, int duration);
|
||||
|
||||
/**
|
||||
* Temporarily displays registered BossBar to all players for specified amount of time in milliseconds.
|
||||
*
|
||||
* @param bossBar
|
||||
* Name of registered BossBar to show
|
||||
* @param duration
|
||||
* In milliseconds for how long should BossBar be displayed
|
||||
* @throws IllegalArgumentException
|
||||
* if no BossBar with specified name exists
|
||||
* @see #sendBossBarTemporarily(TabPlayer, String, int)
|
||||
* @see #getAnnouncedBossBars()
|
||||
*/
|
||||
void announceBossBar(@NonNull String bossBar, int duration);
|
||||
|
||||
/**
|
||||
* Returns list of BossBars which are currently being announced.
|
||||
*
|
||||
* @return list of currently active BossBar announcements
|
||||
* @see #sendBossBarTemporarily(TabPlayer, String, int)
|
||||
* @see #announceBossBar(String, int)
|
||||
*/
|
||||
@NotNull List<BossBar> getAnnouncedBossBars();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* The event bus. This is used to register event listeners/handlers for
|
||||
* handling events that may be fired by TAB.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface EventBus {
|
||||
|
||||
/**
|
||||
* Scans the given listener for any method that is annotated with
|
||||
* {@link Subscribe} and registers it as a listener to this event bus.
|
||||
*
|
||||
* <p>All methods in this class that are annotated with {@link Subscribe}
|
||||
* must meet the following criteria:
|
||||
* <ul>
|
||||
* <li>It must be <strong>public</strong>.</li>
|
||||
* <li>It must <strong>not</strong> be abstract.</li>
|
||||
* <li>It must have exactly <strong>one</strong> parameter.</li>
|
||||
* <li>Its single parameter must be <strong>an event</strong>.</li>
|
||||
* </ul>
|
||||
* If a method does not meet the above criteria, an exception <strong>will</strong>
|
||||
* be thrown.
|
||||
*
|
||||
* @param listener the listener to register
|
||||
* @throws RuntimeException if a method does not meet the criteria
|
||||
*/
|
||||
void register(@NonNull Object listener);
|
||||
|
||||
/**
|
||||
* Registers the given handler to this event bus.
|
||||
*
|
||||
* @param type the class type of the event
|
||||
* @param handler the handler to register
|
||||
* @param <E> the type of the event
|
||||
*/
|
||||
<E extends TabEvent> void register(@NonNull Class<E> type, @NonNull EventHandler<E> handler);
|
||||
|
||||
/**
|
||||
* Scans the given listener for any method that is annotated with
|
||||
* {@link Subscribe} and unregisters it from this event bus.
|
||||
*
|
||||
* @param listener the listener to unregister
|
||||
*/
|
||||
void unregister(@NonNull Object listener);
|
||||
|
||||
/**
|
||||
* Unregisters the given handler from this event bus.
|
||||
*
|
||||
* @param handler the handler to unregister
|
||||
* @param <E> the type of the event
|
||||
*/
|
||||
<E extends TabEvent> void unregister(@NonNull EventHandler<E> handler);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
/**
|
||||
* A handler for an event. This allows listening for events in a more
|
||||
* functional style.
|
||||
*
|
||||
* @param <E> the type of event this handler handles
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EventHandler<E> {
|
||||
|
||||
/**
|
||||
* Called when the given event is fired from an event bus.
|
||||
*
|
||||
* @param event the event to handle
|
||||
*/
|
||||
void handle(E event);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Subscribes to a {@link TabEvent}.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Subscribe {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
/**
|
||||
* An event that may be fired by TAB when something occurs.
|
||||
*
|
||||
* <p>This is a marker interface. All events <strong>must</strong> implement this
|
||||
* interface.
|
||||
*/
|
||||
public interface TabEvent {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package me.neznamy.tab.api.event.player;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Called when the player has been fully loaded by TAB.
|
||||
*/
|
||||
public interface PlayerLoadEvent extends TabEvent {
|
||||
|
||||
/**
|
||||
* Gets the player that was loaded.
|
||||
*
|
||||
* @return the player that was loaded
|
||||
*/
|
||||
@NotNull TabPlayer getPlayer();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player was loaded because they joined,
|
||||
* {@code false} if the player was loaded because tab reload command was executed
|
||||
*
|
||||
* @return {@code true} if player joined, {@code false} if plugin was reloaded
|
||||
*/
|
||||
boolean isJoin();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package me.neznamy.tab.api.event.plugin;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Called when an unknown placeholder is about to be registered.
|
||||
* It defaults to a PlaceholderAPI implementation, but can be overridden.
|
||||
*/
|
||||
public interface PlaceholderRegisterEvent extends TabEvent {
|
||||
|
||||
/**
|
||||
* Returns placeholder identifier starting and ending with {@code %}
|
||||
*
|
||||
* @return placeholder identifier starting and ending with {@code %}
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified server placeholder implementation.
|
||||
*
|
||||
* @param supplier
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setServerPlaceholder(@NonNull Supplier<Object> supplier);
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified player placeholder implementation.
|
||||
*
|
||||
* @param function
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setPlayerPlaceholder(@NonNull Function<TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified relational placeholder implementation.
|
||||
*
|
||||
* @param function
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setRelationalPlaceholder(@NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.neznamy.tab.api.event.plugin;
|
||||
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
|
||||
/**
|
||||
* Called when TAB is fully loaded.
|
||||
*/
|
||||
public interface TabLoadEvent extends TabEvent {
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package me.neznamy.tab.api.nametag;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for manipulating player name tags.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getNameTagManager()}.
|
||||
* This requires the Team feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface NameTagManager {
|
||||
|
||||
/**
|
||||
* Makes player's NameTag globally invisible
|
||||
*
|
||||
* @param player
|
||||
* player to hide name tag of
|
||||
* @see #showNameTag(TabPlayer)
|
||||
* @see #hasHiddenNameTag(TabPlayer)
|
||||
*/
|
||||
void hideNameTag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Hides player's NameTag for specified player until it's shown again
|
||||
*
|
||||
* @param player
|
||||
* player to hide name tag of
|
||||
* @param viewer
|
||||
* player to hide name tag for
|
||||
*/
|
||||
void hideNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Makes player's NameTag visible again
|
||||
*
|
||||
* @param player
|
||||
* player to show name tag of
|
||||
* @see #hideNameTag(TabPlayer)
|
||||
* @see #hasHiddenNameTag(TabPlayer)
|
||||
*/
|
||||
void showNameTag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Shows player's NameTag for specified viewer if it was hidden before
|
||||
*
|
||||
* @param player
|
||||
* player to show name tag of
|
||||
* @param viewer
|
||||
* player to show NameTag back for
|
||||
*/
|
||||
void showNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Return whether player has hidden NameTag or not
|
||||
*
|
||||
* @param player
|
||||
* player to check name tag visibility status of
|
||||
* @return Whether player has hidden NameTag or not
|
||||
* @see #hideNameTag(TabPlayer)
|
||||
* @see #showNameTag(TabPlayer)
|
||||
*/
|
||||
boolean hasHiddenNameTag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns true if NameTag is hidden for specified viewer, false if not
|
||||
*
|
||||
* @param player
|
||||
* player to check visibility status of
|
||||
* @param viewer
|
||||
* player to check visibility status for
|
||||
* @return true if hidden, false if not
|
||||
*/
|
||||
boolean hasHiddenNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
|
||||
* This can be resumed using resumeTeamHandling(). If team handling was paused already, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* player to pause team handling of
|
||||
*/
|
||||
void pauseTeamHandling(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
|
||||
*
|
||||
* @param player
|
||||
* player to resume team handling of
|
||||
*/
|
||||
void resumeTeamHandling(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
|
||||
* it was resumed already using resumeTeamHandling
|
||||
*
|
||||
* @param player
|
||||
* player to check handling status of
|
||||
* @return true if paused, false if not
|
||||
*/
|
||||
boolean hasTeamHandlingPaused(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Forces collision rule for the player. Setting it to null will remove forced value
|
||||
*
|
||||
* @param player
|
||||
* player to set collision rule of
|
||||
* @param collision
|
||||
* forced collision rule
|
||||
*/
|
||||
void setCollisionRule(@NonNull TabPlayer player, @Nullable Boolean collision);
|
||||
|
||||
/**
|
||||
* Returns forced collision rule or null if collision is not forced using setCollisionRule
|
||||
*
|
||||
* @param player
|
||||
* player to get forced collision of
|
||||
* @return forced value or null if not forced
|
||||
*/
|
||||
@Nullable Boolean getCollisionRule(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes player's prefix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param prefix
|
||||
* new prefix value
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
|
||||
|
||||
/**
|
||||
* Changes player's suffix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param suffix
|
||||
* new suffix value
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* On first call, name tags of all players will become invisible for specified
|
||||
* player. On second call, they will become visible again.
|
||||
*
|
||||
* @param player
|
||||
* player to toggle name tag visibility view for
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if configured toggle message should be sent, {@code false} if not
|
||||
*/
|
||||
void toggleNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has hidden name tags by either calling
|
||||
* {@link #toggleNameTagVisibilityView(TabPlayer, boolean)} or using a command,
|
||||
* {@code false} if not.
|
||||
*
|
||||
* @param player
|
||||
* player to check
|
||||
* @return {@code true} if hidden, {@code false} if not
|
||||
*/
|
||||
boolean hasHiddenNameTagVisibilityView(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package me.neznamy.tab.api.nametag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for manipulating player name tags.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getNameTagManager()} and then casting it.
|
||||
* This requires both Team and Unlimited name tag features to be enabled in config, otherwise the method will
|
||||
* return {@code null} or not be an instance of this class.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface UnlimitedNameTagManager extends NameTagManager {
|
||||
|
||||
/**
|
||||
* Disables armor stands of specified player and displays vanilla name tags instead.
|
||||
*
|
||||
* @param player
|
||||
* Player to disable armor stands of
|
||||
* @see #enableArmorStands(TabPlayer)
|
||||
* @see #hasDisabledArmorStands(TabPlayer)
|
||||
*/
|
||||
void disableArmorStands(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Enabled back armor stands of player, who was disabled previously using
|
||||
* {@link #disableArmorStands(TabPlayer)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to enable back
|
||||
* @see #disableArmorStands(TabPlayer)
|
||||
* @see #hasDisabledArmorStands(TabPlayer)
|
||||
*/
|
||||
void enableArmorStands(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has armor stands disabled using
|
||||
* {@link #disableArmorStands(TabPlayer)}, {@code false} otherwise.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if disabled, {@code false} if not.
|
||||
* @see #disableArmorStands(TabPlayer)
|
||||
* @see #enableArmorStands(TabPlayer)
|
||||
*/
|
||||
boolean hasDisabledArmorStands(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes name to specified value. Set to {@code null} to
|
||||
* reset back to original value.
|
||||
*
|
||||
* @param player
|
||||
* Player to change name of
|
||||
* @param customName
|
||||
* New name, or {@code null} to reset to original value
|
||||
*/
|
||||
void setName(@NonNull TabPlayer player, @Nullable String customName);
|
||||
|
||||
/**
|
||||
* Changes line to specified value. Set to {@code null} to reset back to original value.
|
||||
*
|
||||
* @param player
|
||||
* Player to change line of
|
||||
* @param line
|
||||
* Name of defined line
|
||||
* @param value
|
||||
* Value to use, or {@code null} to reset back to original value
|
||||
* @see #getDefinedLines()
|
||||
*/
|
||||
void setLine(@NonNull TabPlayer player, @NonNull String line, @Nullable String value);
|
||||
|
||||
/**
|
||||
* Returns custom name value set using {@link #setName(TabPlayer, String)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return Custom name set previously or {@code null} if not set
|
||||
* @see #setName(TabPlayer, String)
|
||||
*/
|
||||
@Nullable String getCustomName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns custom line value set using {@link #setLine(TabPlayer, String, String)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @param line
|
||||
* Defined line
|
||||
* @return Custom name set previously or {@code null} if not set
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getDefinedLines()
|
||||
*/
|
||||
@Nullable String getCustomLineValue(@NonNull TabPlayer player, @NonNull String line);
|
||||
|
||||
/**
|
||||
* Returns original name value configured in files.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return Original configured name
|
||||
* @see #setName(TabPlayer, String)
|
||||
*/
|
||||
@NotNull String getOriginalName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original line value configured in files.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @param line
|
||||
* Line name
|
||||
* @return Original configured line
|
||||
* @see #setLine(TabPlayer, String, String)
|
||||
*/
|
||||
@NotNull String getOriginalLineValue(@NonNull TabPlayer player, @NonNull String line);
|
||||
|
||||
/**
|
||||
* Returns list of all defined lines in config, both static and dynamic lines.
|
||||
*
|
||||
* @return List of all defined lines in config
|
||||
*/
|
||||
@NotNull List<String> getDefinedLines();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An interface representing some kind of placeholder
|
||||
*/
|
||||
public interface Placeholder {
|
||||
|
||||
/**
|
||||
* Returns refresh interval of this placeholder
|
||||
*
|
||||
* @return refresh interval of this placeholder
|
||||
*/
|
||||
int getRefresh();
|
||||
|
||||
/**
|
||||
* Returns placeholder's identifier
|
||||
*
|
||||
* @return placeholder's identifier
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* An interface allowing placeholder registration. Instance can be
|
||||
* obtained using {@link TabAPI#getPlaceholderManager()}
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface PlaceholderManager {
|
||||
|
||||
/**
|
||||
* Registers a server placeholder (placeholder with same output for all players)
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Refresh interval
|
||||
* @param supplier
|
||||
* Supplier for placeholder output
|
||||
* @return Registered placeholder for further use
|
||||
* @throws IllegalArgumentException
|
||||
* If {@code identifier} does not start and end with {@code %} or
|
||||
* {@code refresh} is not divisible by 50
|
||||
*/
|
||||
@NotNull ServerPlaceholder registerServerPlaceholder(@NonNull String identifier, int refresh, @NonNull Supplier<Object> supplier);
|
||||
|
||||
/**
|
||||
* Registers a player placeholder (placeholder different output per player)
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Refresh interval
|
||||
* @param function
|
||||
* Function for placeholder output
|
||||
* @return Registered placeholder for further use
|
||||
* @throws IllegalArgumentException
|
||||
* If {@code identifier} does not start and end with {@code %} or
|
||||
* {@code refresh} is not divisible by 50
|
||||
*/
|
||||
@NotNull PlayerPlaceholder registerPlayerPlaceholder(@NonNull String identifier, int refresh, @NonNull Function<TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Registers a relational placeholder (placeholder with output different for each player duo)
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Refresh interval
|
||||
* @param function
|
||||
* Function for placeholder output
|
||||
* @return Registered placeholder for further use
|
||||
* @throws IllegalArgumentException
|
||||
* If {@code identifier} does not start and end with {@code %},
|
||||
* does not start with {@code %rel_} or
|
||||
* {@code refresh} is not divisible by 50
|
||||
*/
|
||||
@NotNull RelationalPlaceholder registerRelationalPlaceholder(@NonNull String identifier, int refresh, @NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Returns placeholder from specified identifier. If it does not exist, it is registered
|
||||
* as a PlaceholderAPI placeholder and returned.
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @return Placeholder with specified identifier
|
||||
*/
|
||||
@NotNull Placeholder getPlaceholder(@NonNull String identifier);
|
||||
|
||||
/**
|
||||
* Unregisters placeholder and makes plugin no longer refresh it.
|
||||
*
|
||||
* @param placeholder
|
||||
* Placeholder to unregister
|
||||
*/
|
||||
void unregisterPlaceholder(@NonNull Placeholder placeholder);
|
||||
|
||||
/**
|
||||
* Unregisters placeholder and makes plugin no longer refresh it.
|
||||
*
|
||||
* @param identifier
|
||||
* identifier of placeholder to unregister
|
||||
*/
|
||||
void unregisterPlaceholder(@NonNull String identifier);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
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)
|
||||
*/
|
||||
public interface PlayerPlaceholder extends Placeholder {
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
|
||||
* so this is (only) useful for force an update when value is supposed to change, and you have
|
||||
* a high refresh interval set.
|
||||
*
|
||||
* @param player
|
||||
* Player to update value for
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull TabPlayer player, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*
|
||||
* @param player
|
||||
* Player to update value for
|
||||
*/
|
||||
void update(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
|
||||
* so this is (only) useful for force an update when value is supposed to change, and you have
|
||||
* a high refresh interval set.
|
||||
*
|
||||
* @param viewer
|
||||
* Placeholder viewer
|
||||
* @param target
|
||||
* Placeholder target
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*
|
||||
* @param viewer
|
||||
* Placeholder viewer
|
||||
* @param target
|
||||
* Placeholder target
|
||||
*/
|
||||
void update(@NonNull TabPlayer viewer, @NonNull TabPlayer target);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface for server placeholders (returning same value for all players)
|
||||
*/
|
||||
public interface ServerPlaceholder extends Placeholder {
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
|
||||
* so this is (only) useful for force an update when value is supposed to change, and you have
|
||||
* a high refresh interval set.
|
||||
*
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@Nullable Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*/
|
||||
void update();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An interface allowing to work with a line of
|
||||
* text in scoreboard.
|
||||
*
|
||||
*/
|
||||
public interface Line {
|
||||
|
||||
/**
|
||||
* Returns configured raw text of this line. Placeholders
|
||||
* remain in raw format.
|
||||
*
|
||||
* @return raw text of this line
|
||||
*/
|
||||
@NotNull String getText();
|
||||
|
||||
/**
|
||||
* Changes text to new value. Supports placeholders, which are
|
||||
* automatically registered if needed and refreshed periodically
|
||||
* based on configuration. No need to call this method to try to
|
||||
* keep placeholders up to date. If value is identical to previous
|
||||
* one, nothing happens.
|
||||
*
|
||||
* @param text
|
||||
* Raw text to display in the line
|
||||
*/
|
||||
void setText(@NonNull String text);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An interface allowing to work with scoreboard, such as adding players,
|
||||
* removing players and changing lines. New instance can be created using
|
||||
* {@link ScoreboardManager#createScoreboard(String, String, List)},
|
||||
* for scoreboards from config {@link ScoreboardManager#getRegisteredScoreboards()}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface Scoreboard {
|
||||
|
||||
/**
|
||||
* Returns internal name of this scoreboard. Name is defined in registration,
|
||||
* scoreboards from config use name they were defined with. This value is
|
||||
* used internally and in /tab announce scoreboard command.
|
||||
*
|
||||
* @return custom name of this scoreboard
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns raw title of this scoreboard. Placeholders stay in their
|
||||
* raw format.
|
||||
*
|
||||
* @return scoreboard title
|
||||
* @see #setTitle(String)
|
||||
*/
|
||||
@NotNull String getTitle();
|
||||
|
||||
/**
|
||||
* Sets title to specified value. Placeholders are refreshed automatically
|
||||
* with refresh intervals defined in config. No need to call this method
|
||||
* to try to keep placeholders up to date. Supports RGB codes using any of
|
||||
* the supported formats.
|
||||
* <p>
|
||||
* Length is limited to 32 characters on <1.13. If the limit is exceeded,
|
||||
* text will be cut to 32 characters.
|
||||
* <p>
|
||||
* Calling this method with same title as before will not do anything.
|
||||
*
|
||||
* @param title
|
||||
* New title to use with placeholder support
|
||||
* @see #getTitle()
|
||||
*/
|
||||
void setTitle(@NonNull String title);
|
||||
|
||||
/**
|
||||
* Returns list of lines in this scoreboard in the order they appear
|
||||
* in game (first line is on top). This list should only be used for reading,
|
||||
* for adding/removing lines see {@link #addLine(String)} and {@link #removeLine(int)}.
|
||||
*
|
||||
* @return list of lines in this scoreboard
|
||||
* @see #addLine(String)
|
||||
* @see #removeLine(int)
|
||||
*/
|
||||
@NotNull List<Line> getLines();
|
||||
|
||||
/**
|
||||
* Adds line with specified text on the bottom of scoreboard. Supports
|
||||
* placeholders, which will automatically be refreshed.
|
||||
*
|
||||
* @param text
|
||||
* Text to display
|
||||
* @see #getLines()
|
||||
* @see #removeLine(int)
|
||||
*/
|
||||
void addLine(@NonNull String text);
|
||||
|
||||
/**
|
||||
* Removes line with specified index. Index starts at {@code 0} and ends at
|
||||
* {@link #getLines()}.size()-1.
|
||||
*
|
||||
* @param index
|
||||
* Index of line to remove, starting at 0
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the index is out of range (index < 0 || index >= {@link #getLines()}.size())
|
||||
* @see #getLines()
|
||||
* @see #addLine(String)
|
||||
*/
|
||||
void removeLine(int index);
|
||||
|
||||
/**
|
||||
* Unregisters this scoreboard from all players who can see it.
|
||||
*/
|
||||
void unregister();
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface allowing work with scoreboards, such as creating,
|
||||
* sending, toggling and modifying.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link me.neznamy.tab.api.TabAPI#getScoreboardManager()}.
|
||||
* This requires the scoreboard feature to be enabled in config,
|
||||
* the method will return {@code null} otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface ScoreboardManager {
|
||||
|
||||
/**
|
||||
* Creates new scoreboard, registers and returns it. {@code name}
|
||||
* is internal name of the scoreboard, which it will be registered
|
||||
* with and further work where scoreboard name is used allowed.
|
||||
* <p>
|
||||
* Scoreboard {@code title} is limited to 32 characters on <1.13. If needed,
|
||||
* it will be cut to 32 character for those players (after replacing
|
||||
* placeholders). RGB is supported using any of the supported formats.
|
||||
* Placeholders are supported.
|
||||
* <p>
|
||||
* {@code lines} will display in the same order as contained in the list.
|
||||
* Client can only see up to 15 lines at a time, however you can define
|
||||
* more, if some of them only consist of placeholder that might be empty.
|
||||
*
|
||||
* @param name
|
||||
* Internal name of the scoreboard
|
||||
* @param title
|
||||
* The scoreboard title
|
||||
* @param lines
|
||||
* Lines of text in scoreboard (supports placeholders)
|
||||
* @return The new scoreboard with given parameters
|
||||
* @throws NullPointerException
|
||||
* if {@code name} or {@code lines} is {@code null}
|
||||
*/
|
||||
@NotNull Scoreboard createScoreboard(@NonNull String name, @NonNull String title, @NonNull List<String> lines);
|
||||
|
||||
/**
|
||||
* Returns map of registered scoreboards via config and API.
|
||||
* Map key is name of scoreboard, which is defined either in config
|
||||
* or using {@link #createScoreboard(String, String, List)}.
|
||||
* Map value is reference to the actual scoreboard.
|
||||
*
|
||||
* @return map of registered scoreboards
|
||||
*/
|
||||
@NotNull Map<String, Scoreboard> getRegisteredScoreboards();
|
||||
|
||||
/**
|
||||
* Displays scoreboard to defined player. This will disable all display
|
||||
* condition checks until the scoreboard is unregistered using
|
||||
* {@link #resetScoreboard(TabPlayer)}. If the player already sees
|
||||
* this scoreboard, nothing will happen.
|
||||
*
|
||||
* @param player
|
||||
* Player to send scoreboard to
|
||||
* @param scoreboard
|
||||
* Scoreboard to display
|
||||
* @throws NullPointerException
|
||||
* if {@code player} or {@code scoreboard} is {@code null}
|
||||
* @see #hasCustomScoreboard(TabPlayer)
|
||||
* @see #resetScoreboard(TabPlayer)
|
||||
*/
|
||||
void showScoreboard(@NonNull TabPlayer player, @NonNull Scoreboard scoreboard);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has custom scoreboard set using
|
||||
* {@link #showScoreboard(TabPlayer, Scoreboard)}, {@code false} if not.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if player has custom scoreboard set using API,
|
||||
* {@code false} if not
|
||||
* @see #showScoreboard(TabPlayer, Scoreboard)
|
||||
* @see #resetScoreboard(TabPlayer)
|
||||
*/
|
||||
boolean hasCustomScoreboard(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Hides custom scoreboard sent using {@link #showScoreboard(TabPlayer, Scoreboard)}
|
||||
* and re-enables internal display logic with conditions. If player does not
|
||||
* have any forced scoreboard, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* Player to hide custom scoreboard from
|
||||
* @see #showScoreboard(TabPlayer, Scoreboard)
|
||||
* @see #hasCustomScoreboard(TabPlayer)
|
||||
*/
|
||||
void resetScoreboard(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has scoreboard enabled, {@code false}
|
||||
* if disabled (toggled) using either toggle command,
|
||||
* {@link #toggleScoreboard(TabPlayer, boolean)} or
|
||||
* {@link #setScoreboardVisible(TabPlayer, boolean, boolean)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get visibility status of
|
||||
* @return {@code true} if visible, {@code false} if disabled
|
||||
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
|
||||
* @see #toggleScoreboard(TabPlayer, boolean)
|
||||
*/
|
||||
boolean hasScoreboardVisible(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sets scoreboard visibility of player to defined value. If visibility status
|
||||
* is same as player had before, nothing happens. If this call changes
|
||||
* visibility, scoreboard is toggled. If {@code sendToggleMessage} is {@code true},
|
||||
* toggle message defined in configuration is sent.
|
||||
*
|
||||
* @param player
|
||||
* Player to set visibility for
|
||||
* @param visible
|
||||
* New visibility status
|
||||
* @param sendToggleMessage
|
||||
* whether to send toggle message if status changed or not
|
||||
* @see #toggleScoreboard(TabPlayer, boolean)
|
||||
* @see #hasScoreboardVisible(TabPlayer)
|
||||
*/
|
||||
void setScoreboardVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Toggles scoreboard for specified player. If player had scoreboard visible,
|
||||
* hides it. If hidden, shows it. If {@code sendToggleMessage} is {@code true},
|
||||
* toggle message defined in configuration is sent.
|
||||
*
|
||||
* @param player
|
||||
* Player to toggle scoreboard for
|
||||
* @param sendToggleMessage
|
||||
* Whether to send toggle message or not
|
||||
* @see #hasScoreboardVisible(TabPlayer)
|
||||
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
void toggleScoreboard(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Temporarily displays scoreboard to all players for specified amount of
|
||||
* time (in milliseconds). Scoreboard name is either defined in config, or
|
||||
* through API in {@link #createScoreboard(String, String, List)}.
|
||||
*
|
||||
* @param scoreboard
|
||||
* Scoreboard from config or registered via API
|
||||
* @param duration
|
||||
* Duration in milliseconds
|
||||
* @throws IllegalArgumentException
|
||||
* if no scoreboard was found with such name or {@code duration}
|
||||
* is < 0.
|
||||
*/
|
||||
void announceScoreboard(@NonNull String scoreboard, int duration);
|
||||
|
||||
/**
|
||||
* Returns player's currently displayed scoreboard. This can be either with
|
||||
* configuration, overridden with commands or the API. Will return {@code null}
|
||||
* if player does not see any scoreboard due to not meeting any display condition.
|
||||
*
|
||||
* @param player
|
||||
* player to get active scoreboard of
|
||||
* @return player's active scoreboard or {@code null} if player has no scoreboard
|
||||
*/
|
||||
@Nullable Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface allowing to send header/footer to players.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getHeaderFooterManager()}.
|
||||
* This requires the Header/footer feature to be enabled in config, otherwise
|
||||
* the method will return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface HeaderFooterManager {
|
||||
|
||||
/**
|
||||
* Sets raw value of header to specified value. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value. Use {@code null} to reset value back to original.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param header
|
||||
* Raw header value
|
||||
*/
|
||||
void setHeader(@NonNull TabPlayer player, @Nullable String header);
|
||||
|
||||
/**
|
||||
* Sets raw value of footer to specified value. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value. Use {@code null} to reset value back to original.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param footer
|
||||
* Raw footer value
|
||||
*/
|
||||
void setFooter(@NonNull TabPlayer player, @Nullable String footer);
|
||||
|
||||
/**
|
||||
* Sets raw value of header and footer to specified values. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value. Use {@code null} to reset value back to original.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param header
|
||||
* Raw header value
|
||||
* @param footer
|
||||
* Raw footer value
|
||||
*/
|
||||
void setHeaderAndFooter(@NonNull TabPlayer player, @Nullable String header, @Nullable String footer);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Forces new team name for the player until this method is called again with null argument and
|
||||
* performs all actions to change player's team name
|
||||
*
|
||||
* @param player
|
||||
* player to set team name of
|
||||
* @param teamName
|
||||
* forced team name
|
||||
*/
|
||||
void forceTeamName(@NonNull TabPlayer player, @Nullable String teamName);
|
||||
|
||||
/**
|
||||
* Returns forced team name of player or null if not forced
|
||||
*
|
||||
* @param player
|
||||
* player to check forced team name of
|
||||
* @return forced team name of player or null if not forced
|
||||
*/
|
||||
@Nullable String getForcedTeamName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original team name of the player set by the plugin.
|
||||
*
|
||||
* @param player
|
||||
* Player to get team name of
|
||||
* @return Player's team name based on configuration.
|
||||
*/
|
||||
@NotNull String getOriginalTeamName(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface allowing to modify TabList display names
|
||||
* of players.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getTabListFormatManager()}.
|
||||
* This requires the feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface TabListFormatManager {
|
||||
|
||||
/**
|
||||
* Changes player's prefix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param prefix
|
||||
* new prefix value
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
|
||||
|
||||
/**
|
||||
* Changes player's name to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param customName
|
||||
* new customName value
|
||||
* @see #getCustomName(TabPlayer)
|
||||
* @see #getOriginalName(TabPlayer)
|
||||
*/
|
||||
void setName(@NonNull TabPlayer player, @Nullable String customName);
|
||||
|
||||
/**
|
||||
* Changes player's suffix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param suffix
|
||||
* new suffix value
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
/**
|
||||
* Returns prefix assigned using {@link #setPrefix(TabPlayer, String)} method.
|
||||
* If the method was not used, {@code null} is returned.
|
||||
*
|
||||
* @param player
|
||||
* player to get custom prefix of
|
||||
* @return player's custom prefix or {@code null} if not defined
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns custom name assigned using {@link #setName(TabPlayer, String)} method.
|
||||
* If the method was not used, {@code null} is returned.
|
||||
*
|
||||
* @param player
|
||||
* player to get custom name of
|
||||
* @return player's custom name or {@code null} if not defined
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getOriginalName(TabPlayer)
|
||||
*/
|
||||
@Nullable String getCustomName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns suffix assigned using {@link #setSuffix(TabPlayer, String)} method.
|
||||
* If the method was not used, {@code null} is returned.
|
||||
*
|
||||
* @param player
|
||||
* player to get custom suffix of
|
||||
* @return player's custom suffix or {@code null} if not defined
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original prefix applied using plugin's internal logic.
|
||||
* This value is not affected by API calls in any way.
|
||||
*
|
||||
* @param player
|
||||
* player to get original prefix of
|
||||
* @return player's original prefix
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
*/
|
||||
@NotNull String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original name applied using plugin's internal logic.
|
||||
* This value is not affected by API calls in any way.
|
||||
*
|
||||
* @param player
|
||||
* player to get original name of
|
||||
* @return player's original name
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getCustomName(TabPlayer)
|
||||
*/
|
||||
@NotNull String getOriginalName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original suffix applied using plugin's internal logic.
|
||||
* This value is not affected by API calls in any way.
|
||||
*
|
||||
* @param player
|
||||
* player to get original suffix of
|
||||
* @return player's original suffix
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
*/
|
||||
@NotNull String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package me.neznamy.tab.api.tablist.layout;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface representing a layout.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface Layout {
|
||||
|
||||
/**
|
||||
* Returns name of the layout.
|
||||
*
|
||||
* @return name of the layout
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text);
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
* @param skin
|
||||
* Skin definition like in config
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin);
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
* @param ping
|
||||
* Slot's ping
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, int ping);
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
* @param skin
|
||||
* Skin definition like in config
|
||||
* @param ping
|
||||
* Slot's ping
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin, int ping);
|
||||
|
||||
/**
|
||||
* Adds a player group.
|
||||
*
|
||||
* @param condition
|
||||
* Condition that must be met for player to appear in the group.
|
||||
* May be null for no requirement. Syntax is like in config -
|
||||
* either condition name or short format.
|
||||
* @param slots
|
||||
* Slots for this player group
|
||||
*/
|
||||
void addGroup(@Nullable String condition, int[] slots);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package me.neznamy.tab.api.tablist.layout;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for working with layouts. Instance can be obtained using
|
||||
* {@link TabAPI#getLayoutManager()}. If the feature is disabled in
|
||||
* config, it will return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface LayoutManager {
|
||||
|
||||
/**
|
||||
* Creates new layout
|
||||
*
|
||||
* @param name
|
||||
* Unique layout name
|
||||
* @return Created layout
|
||||
*/
|
||||
Layout createNewLayout(String name);
|
||||
|
||||
/**
|
||||
* Sends layout to player. Set to {@code null} to make player not see
|
||||
* any layout, but default TabList instead.
|
||||
*
|
||||
* @param player
|
||||
* Player to send layout to
|
||||
* @param layout
|
||||
* Layout to send
|
||||
* @see #resetLayout(TabPlayer)
|
||||
*/
|
||||
void sendLayout(@NonNull TabPlayer player, @Nullable Layout layout);
|
||||
|
||||
/**
|
||||
* Reset layout back to original one based on configuration.
|
||||
*
|
||||
* @param player
|
||||
* Player to reset layout for
|
||||
* @see #sendLayout(TabPlayer, Layout)
|
||||
*/
|
||||
void resetLayout(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.johnrengelman:shadow:8.1.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.6")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.6-SNAPSHOT")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
rootProject.name = "build-logic"
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
maven("https://maven.fabricmc.net/")
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
id("io.freefair.lombok")
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
filter<ReplaceTokens>("tokens" to mapOf(
|
||||
"id" to rootProject.ext.get("id")!!.toString(),
|
||||
"name" to rootProject.name,
|
||||
"version" to project.version,
|
||||
"description" to project.description,
|
||||
"website" to rootProject.ext.get("website")!!.toString(),
|
||||
"author" to rootProject.ext.get("author")!!.toString()
|
||||
))
|
||||
}
|
||||
javadoc {
|
||||
// This saves a decent bit of processing power on slow machines
|
||||
enabled = false
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
|
||||
}
|
||||
compileJava {
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
options.release.set(8)
|
||||
options.compilerArgs.addAll(listOf("-nowarn", "-Xlint:-unchecked", "-Xlint:-deprecation"))
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
@@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
id("tab.standard-conventions")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
}
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
archiveFileName.set("TAB-${project.name}-${project.version}.jar")
|
||||
relocate("org.bstats", "me.neznamy.tab.libs.org.bstats")
|
||||
relocate("org.json.simple", "me.neznamy.tab.libs.org.json.simple")
|
||||
relocate("net.kyori.event", "me.neznamy.tab.libs.net.kyori.event")
|
||||
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.me.neznamy.yamlassist")
|
||||
relocate("org.yaml.snakeyaml", "me.neznamy.tab.libs.org.yaml.snakeyaml")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id("tab.base-conventions")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "krypton"
|
||||
url = uri("https://repo.kryptonmc.org/releases")
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
}
|
||||
publications.create<MavenPublication>("mavenJava") {
|
||||
groupId = rootProject.group as String
|
||||
artifactId = "tab-${project.name}"
|
||||
version = rootProject.version as String
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
plugins {
|
||||
id("tab.parent")
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "4.1.4"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
ext.set("website", "https://github.com/NEZNAMY/TAB")
|
||||
ext.set("author", "NEZNAMY")
|
||||
}
|
||||
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bungeecord,
|
||||
projects.velocity,
|
||||
projects.sponge7,
|
||||
projects.sponge8,
|
||||
projects.fabric,
|
||||
projects.fabric.v1144
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
projects.api,
|
||||
projects.shared
|
||||
).map { it.dependencyProject }
|
||||
|
||||
subprojects {
|
||||
when (this) {
|
||||
in platforms -> plugins.apply("tab.platform-conventions")
|
||||
in special -> plugins.apply("tab.standard-conventions")
|
||||
else -> plugins.apply("tab.base-conventions")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bukkit:3.0.1")
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
|
||||
compileOnly("me.clip:placeholderapi:2.11.2")
|
||||
compileOnly("com.mojang:authlib:1.5.25")
|
||||
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
|
||||
exclude("org.bukkit", "bukkit")
|
||||
}
|
||||
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
|
||||
exclude("org.spigotmc", "spigot")
|
||||
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")
|
||||
}
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>me.neznamy</groupId>
|
||||
<artifactId>TAB</artifactId>
|
||||
<version>2.8.11-pre18</version>
|
||||
</parent>
|
||||
<artifactId>bukkit</artifactId>
|
||||
|
||||
<repositories>
|
||||
|
||||
<!-- Spigot API -->
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
|
||||
<!-- PlaceholderAPI -->
|
||||
<repository>
|
||||
<id>placeholderapi</id>
|
||||
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
|
||||
</repository>
|
||||
|
||||
<!-- Vault -->
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
|
||||
<!-- ViaVersion -->
|
||||
<repository>
|
||||
<id>viaversion</id>
|
||||
<url>https://repo.viaversion.com/</url>
|
||||
</repository>
|
||||
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- TAB Core -->
|
||||
<dependency>
|
||||
<groupId>me.neznamy</groupId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>YamlAssist</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Spigot API -->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.16.5-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-chat</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- PlaceholderAPI -->
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.10.9</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Vault -->
|
||||
<dependency>
|
||||
<groupId>com.github.MilkBowl</groupId>
|
||||
<artifactId>VaultAPI</artifactId>
|
||||
<version>1.7</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>bukkit</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- ViaVersion -->
|
||||
<dependency>
|
||||
<groupId>com.viaversion</groupId>
|
||||
<artifactId>viaversion-api</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,5 +1,10 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -7,51 +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 me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The core for bukkit forwarding events into all enabled features
|
||||
*/
|
||||
public class BukkitEventListener implements Listener {
|
||||
public class BukkitEventListener implements EventListener<Player>, Listener {
|
||||
|
||||
/**
|
||||
* Listener to PlayerQuitEvent to remove player data and forward the event to features
|
||||
* @param e quit event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e){
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask("processing PlayerQuitEvent", () -> TAB.getInstance().getFeatureManager().onQuit(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerJoinEvent to create player data and forward the event to features
|
||||
* @param e join event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask("processing PlayerJoinEvent", () -> TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer())));
|
||||
}
|
||||
/**
|
||||
* Listens to player quit event.
|
||||
*
|
||||
* @param e
|
||||
* Disconnect event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e) {
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerChangedWorldEvent to forward the event to features
|
||||
* @param e world changed event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e){
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask("processing PlayerChangedWorldEvent", () -> TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
|
||||
}
|
||||
/**
|
||||
* Listens to player join event.
|
||||
*
|
||||
* @param e
|
||||
* Join event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
join(e.getPlayer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerChangedWorldEvent to forward the event to features
|
||||
* @param e command preprocess event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onCommand(PlayerCommandPreprocessEvent e) {
|
||||
if (TAB.getInstance().isDisabled()) return;
|
||||
if (TAB.getInstance().getFeatureManager().onCommand(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
@NotNull
|
||||
public TabPlayer createPlayer(@NotNull Player player) {
|
||||
return new BukkitTabPlayer((BukkitPlatform) TAB.getInstance().getPlatform(), player);
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.ServicePriority;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.Metrics;
|
||||
|
||||
/**
|
||||
* bStats metrics for bukkit
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class BukkitMetrics extends Metrics {
|
||||
|
||||
private Main plugin;
|
||||
|
||||
public BukkitMetrics(Main plugin) {
|
||||
super("https://bStats.org/submitData/bukkit");
|
||||
if (plugin == null) {
|
||||
throw new IllegalArgumentException("Plugin cannot be null!");
|
||||
}
|
||||
this.plugin = plugin;
|
||||
|
||||
// Get the config file
|
||||
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
|
||||
File configFile = new File(bStatsFolder, "config.yml");
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
|
||||
if (!config.isSet("serverUuid")) {
|
||||
config.addDefault("enabled", true);
|
||||
config.addDefault("serverUuid", UUID.randomUUID().toString());
|
||||
config.addDefault("logFailedRequests", false);
|
||||
config.addDefault("logSentData", false);
|
||||
config.addDefault("logResponseStatusText", false);
|
||||
config.options().header(
|
||||
"bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
|
||||
"To honor their work, you should not disable it.\n" +
|
||||
"This has nearly no effect on the server performance!\n" +
|
||||
"Check out https://bStats.org/ to learn more :)"
|
||||
).copyDefaults(true);
|
||||
try {
|
||||
config.save(configFile);
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
|
||||
// Load the data
|
||||
enabled = config.getBoolean("enabled", true);
|
||||
serverUUID = config.getString("serverUuid");
|
||||
logFailedRequests = config.getBoolean("logFailedRequests", false);
|
||||
logSentData = config.getBoolean("logSentData", false);
|
||||
logResponseStatusText = config.getBoolean("logResponseStatusText", false);
|
||||
|
||||
if (enabled) {
|
||||
boolean found = false;
|
||||
// Search for all other bStats Metrics classes to see if we are the first one
|
||||
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
|
||||
try {
|
||||
service.getField("B_STATS_VERSION"); // Our identifier :)
|
||||
found = true; // We aren't the first
|
||||
break;
|
||||
} catch (NoSuchFieldException ignored) { }
|
||||
}
|
||||
// Register our service
|
||||
Bukkit.getServicesManager().register(BukkitMetrics.class, this, plugin, ServicePriority.Normal);
|
||||
if (!found) {
|
||||
// We are the first!
|
||||
startSubmitting();
|
||||
}
|
||||
}
|
||||
addCustomChart(new SimplePie("unlimited_nametag_mode_enabled", new Callable<String>() {
|
||||
public String call() {
|
||||
return TAB.getInstance().getFeatureManager().isFeatureEnabled("nametagx") ? "Yes" : "No";
|
||||
}
|
||||
}));
|
||||
addCustomChart(new SimplePie("placeholderapi", new Callable<String>() {
|
||||
public String call() {
|
||||
return Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI") ? "Yes" : "No";
|
||||
}
|
||||
}));
|
||||
addCustomChart(new SimplePie("permission_system", new Callable<String>() {
|
||||
public String call() {
|
||||
return TAB.getInstance().getPermissionPlugin().getName();
|
||||
}
|
||||
}));
|
||||
addCustomChart(new SimplePie("server_version", new Callable<String>() {
|
||||
public String call() {
|
||||
return "1." + NMSStorage.getInstance().minorVersion + ".x";
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void startSubmitting() {
|
||||
Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags
|
||||
timer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!plugin.isEnabled()) { // Plugin was disabled
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
// Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler
|
||||
// Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;)
|
||||
Bukkit.getScheduler().runTask(plugin, () -> submitData());
|
||||
}
|
||||
}, 1000*60*5, 1000*60*30);
|
||||
// Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start
|
||||
// WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
|
||||
// WARNING: Just don't do it!
|
||||
}
|
||||
|
||||
private JSONObject getServerData() {
|
||||
// Minecraft specific data
|
||||
int playerAmount = TAB.getInstance().getPlayers().size();
|
||||
int onlineMode = Bukkit.getOnlineMode() ? 1 : 0;
|
||||
String bukkitVersion = Bukkit.getVersion();
|
||||
|
||||
// OS/Java specific data
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
String osName = System.getProperty("os.name");
|
||||
String osArch = System.getProperty("os.arch");
|
||||
String osVersion = System.getProperty("os.version");
|
||||
int coreCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
|
||||
data.put("serverUUID", serverUUID);
|
||||
|
||||
data.put("playerAmount", playerAmount);
|
||||
data.put("onlineMode", onlineMode);
|
||||
data.put("bukkitVersion", bukkitVersion);
|
||||
|
||||
data.put("javaVersion", javaVersion);
|
||||
data.put("osName", osName);
|
||||
data.put("osArch", osArch);
|
||||
data.put("osVersion", osVersion);
|
||||
data.put("coreCount", coreCount);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void submitData() {
|
||||
JSONObject data = getServerData();
|
||||
|
||||
JSONArray pluginData = new JSONArray();
|
||||
// Search for all other bStats Metrics classes to get their plugin data
|
||||
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
|
||||
try {
|
||||
service.getField("B_STATS_VERSION"); // Our identifier :)
|
||||
|
||||
for (RegisteredServiceProvider<?> provider : Bukkit.getServicesManager().getRegistrations(service)) {
|
||||
try {
|
||||
pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider()));
|
||||
} catch (NullPointerException ignored) {
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
} catch (IllegalAccessException ignored) {
|
||||
} catch (InvocationTargetException ignored) {
|
||||
}
|
||||
}
|
||||
} catch (NoSuchFieldException ignored) { }
|
||||
}
|
||||
|
||||
data.put("plugins", pluginData);
|
||||
|
||||
// Create a new thread for the connection to the bStats server
|
||||
new Thread(new Runnable() {
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
// Send the data
|
||||
sendData(data, logResponseStatusText);
|
||||
} catch (Throwable e) {
|
||||
// Something went wrong! :(
|
||||
if (logFailedRequests) {
|
||||
plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -1,578 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.viaversion.viaversion.api.type.Type;
|
||||
import com.viaversion.viaversion.libs.gson.JsonParser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.packets.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.packets.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.packets.PacketBuilder;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutBoss;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutBoss.Action;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutChat;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.EnumGamemode;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerListHeaderFooter;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardDisplayObjective;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardScore;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardTeam;
|
||||
import me.neznamy.tab.shared.packets.PacketPlayOutTitle;
|
||||
import me.neznamy.tab.shared.packets.IChatBaseComponent.ClickAction;
|
||||
import me.neznamy.tab.shared.rgb.TextColor;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class BukkitPacketBuilder implements PacketBuilder {
|
||||
|
||||
//nms storage
|
||||
private NMSStorage nms;
|
||||
|
||||
//entity type ids
|
||||
private Map<EntityType, Integer> entityIds = new HashMap<EntityType, Integer>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
* @param nms - nms storage
|
||||
*/
|
||||
public BukkitPacketBuilder(NMSStorage nms) {
|
||||
this.nms = nms;
|
||||
if (nms.minorVersion >= 13) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 1);
|
||||
entityIds.put(EntityType.WITHER, 83);
|
||||
} else {
|
||||
entityIds.put(EntityType.WITHER, 64);
|
||||
if (nms.minorVersion >= 8){
|
||||
entityIds.put(EntityType.ARMOR_STAND, 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws Exception {
|
||||
if (nms.minorVersion >= 9) {
|
||||
//1.9+ server
|
||||
return buildBossPacket19(packet, clientVersion);
|
||||
}
|
||||
if (clientVersion.getMinorVersion() >= 9 && Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) {
|
||||
//1.9+ client on 1.8 server
|
||||
//technically redundant VV check as there is no other way to get 1.9 client on 1.8 server
|
||||
return buildBossPacketVia(packet, clientVersion);
|
||||
}
|
||||
|
||||
//<1.9 client and server
|
||||
return buildBossPacketEntity(packet, clientVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build 1.9 boss packet
|
||||
* @param packet - packet to build
|
||||
* @param clientVersion - client version
|
||||
* @return 1.9 bossbar packet
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
private Object buildBossPacket19(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws Exception {
|
||||
Object nmsPacket = nms.newPacketPlayOutBoss.newInstance();
|
||||
nms.PacketPlayOutBoss_UUID.set(nmsPacket, packet.id);
|
||||
nms.PacketPlayOutBoss_ACTION.set(nmsPacket, Enum.valueOf(nms.PacketPlayOutBoss_Action, packet.operation.toString()));
|
||||
nms.PacketPlayOutBoss_PROGRESS.set(nmsPacket, packet.pct);
|
||||
nms.PacketPlayOutBoss_NAME.set(nmsPacket, packet.name == null ? null : toNMSComponent(IChatBaseComponent.optimizedComponent(packet.name), clientVersion));
|
||||
nms.PacketPlayOutBoss_COLOR.set(nmsPacket, packet.color == null ? null : Enum.valueOf(nms.BarColor, packet.color.toString()));
|
||||
nms.PacketPlayOutBoss_STYLE.set(nmsPacket, packet.overlay == null ? null : Enum.valueOf(nms.BarStyle, packet.overlay.toString()));
|
||||
nms.PacketPlayOutBoss_DARKEN_SKY.set(nmsPacket, packet.darkenScreen);
|
||||
nms.PacketPlayOutBoss_PLAY_MUSIC.set(nmsPacket, packet.playMusic);
|
||||
nms.PacketPlayOutBoss_CREATE_FOG.set(nmsPacket, packet.createWorldFog);
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds 1.9 bossbar packet bytebuf using viaversion
|
||||
* @param packet - packet to build
|
||||
* @param clientVersion - client version
|
||||
* @return bytebuf with 1.9 bossbar packet content
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
private ByteBuf buildBossPacketVia(PacketPlayOutBoss packet, ProtocolVersion clientVersion) {
|
||||
if (clientVersion == ProtocolVersion.UNKNOWN) return null; //preventing disconnect if packet ID changes and users do not update
|
||||
try {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
Type.VAR_INT.writePrimitive(buf, clientVersion.getMinorVersion() == 15 || clientVersion.getMinorVersion() >= 17 ? 0x0D : 0x0C);
|
||||
Type.UUID.write(buf, packet.id);
|
||||
Type.VAR_INT.writePrimitive(buf, packet.operation.ordinal());
|
||||
switch (packet.operation) {
|
||||
case ADD:
|
||||
Type.COMPONENT.write(buf, JsonParser.parseString(IChatBaseComponent.optimizedComponent(packet.name).toString(clientVersion)));
|
||||
Type.FLOAT.writePrimitive(buf, packet.pct);
|
||||
Type.VAR_INT.writePrimitive(buf, packet.color.ordinal());
|
||||
Type.VAR_INT.writePrimitive(buf, packet.overlay.ordinal());
|
||||
Type.BYTE.write(buf, packet.getFlags());
|
||||
break;
|
||||
case REMOVE:
|
||||
break;
|
||||
case UPDATE_PCT:
|
||||
Type.FLOAT.writePrimitive(buf, packet.pct);
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
Type.COMPONENT.write(buf, JsonParser.parseString(IChatBaseComponent.optimizedComponent(packet.name).toString(clientVersion)));
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
Type.VAR_INT.writePrimitive(buf, packet.color.ordinal());
|
||||
Type.VAR_INT.writePrimitive(buf, packet.overlay.ordinal());
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
Type.BYTE.write(buf, packet.getFlags());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return buf;
|
||||
} catch (Throwable t) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to create 1.9 bossbar packet using ViaVersion v" + Bukkit.getPluginManager().getPlugin("ViaVersion").getDescription().getVersion() + ". Is it the latest version?", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity bossbar packet
|
||||
* @param packet - packet to build
|
||||
* @param clientVersion - client version
|
||||
* @return entity bossbar packet
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws Exception {
|
||||
if (packet.operation == Action.UPDATE_STYLE) return null; //nothing to do here
|
||||
|
||||
int entityId = Math.abs(packet.id.hashCode());
|
||||
if (packet.operation == Action.REMOVE) {
|
||||
return buildEntityDestroyPacket(entityId);
|
||||
}
|
||||
DataWatcher w = new DataWatcher();
|
||||
if (packet.operation == Action.UPDATE_PCT || packet.operation == Action.ADD) {
|
||||
float health = (float)300*packet.pct;
|
||||
if (health == 0) health = 1;
|
||||
w.helper().setHealth(health);
|
||||
}
|
||||
if (packet.operation == Action.UPDATE_NAME || packet.operation == Action.ADD) {
|
||||
w.helper().setCustomName(packet.name, clientVersion);
|
||||
}
|
||||
if (packet.operation == Action.ADD) {
|
||||
w.helper().setEntityFlags((byte) 32);
|
||||
return buildEntitySpawnPacket(entityId, null, EntityType.WITHER, new Location(null, 0,0,0), w);
|
||||
} else {
|
||||
return buildEntityMetadataPacket(entityId, w);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws Exception {
|
||||
Object nmsPacket = nms.newPacketPlayOutChat.newInstance();
|
||||
nms.PacketPlayOutChat_MESSAGE.set(nmsPacket, stringToComponent(packet.message.toString(clientVersion))); //using old method for chat packets due to broken item/entity deserializer
|
||||
if (nms.minorVersion >= 12) {
|
||||
nms.PacketPlayOutChat_POSITION.set(nmsPacket, Enum.valueOf(nms.ChatMessageType, packet.type.toString()));
|
||||
} else if (nms.minorVersion >= 8) {
|
||||
nms.PacketPlayOutChat_POSITION.set(nmsPacket, (byte)packet.type.ordinal());
|
||||
}
|
||||
if (nms.minorVersion >= 16) {
|
||||
nms.PacketPlayOutChat_SENDER.set(nmsPacket, UUID.randomUUID());
|
||||
}
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws Exception {
|
||||
if (nms.minorVersion < 8) return null;
|
||||
Object nmsPacket = nms.newPacketPlayOutPlayerInfo.newInstance();
|
||||
nms.PacketPlayOutPlayerInfo_ACTION.set(nmsPacket, Enum.valueOf(nms.EnumPlayerInfoAction, packet.action.toString()));
|
||||
List<Object> items = new ArrayList<Object>();
|
||||
for (PlayerInfoData data : packet.entries) {
|
||||
Object profile = nms.newGameProfile.newInstance(data.uniqueId, data.name);
|
||||
if (data.skin != null) nms.PropertyMap_putAll.invoke(nms.GameProfile_PROPERTIES.get(profile), data.skin);
|
||||
if (nms.newPlayerInfoData.getParameterCount() == 5) {
|
||||
items.add(nms.newPlayerInfoData.newInstance(nms.newPacketPlayOutPlayerInfo.newInstance(), profile, data.latency, data.gameMode == null ? null : Enum.valueOf(nms.EnumGamemode, data.gameMode.toString()),
|
||||
data.displayName == null ? null : toNMSComponent(data.displayName, clientVersion)));
|
||||
} else {
|
||||
//1.8.8 paper
|
||||
items.add(nms.newPlayerInfoData.newInstance(profile, data.latency, data.gameMode == null ? null : Enum.valueOf(nms.EnumGamemode, data.gameMode.toString()),
|
||||
data.displayName == null ? null : toNMSComponent(data.displayName, clientVersion)));
|
||||
}
|
||||
}
|
||||
nms.PacketPlayOutPlayerInfo_PLAYERS.set(nmsPacket, items);
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws Exception {
|
||||
if (nms.PacketPlayOutPlayerListHeaderFooter == null) return null;
|
||||
Object nmsPacket = nms.newPacketPlayOutPlayerListHeaderFooter.newInstance();
|
||||
nms.PacketPlayOutPlayerListHeaderFooter_HEADER.set(nmsPacket, toNMSComponent(packet.header, clientVersion));
|
||||
nms.PacketPlayOutPlayerListHeaderFooter_FOOTER.set(nmsPacket, toNMSComponent(packet.footer, clientVersion));
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws Exception {
|
||||
Object nmsPacket = nms.newPacketPlayOutScoreboardDisplayObjective.newInstance();
|
||||
nms.PacketPlayOutScoreboardDisplayObjective_POSITION.set(nmsPacket, packet.slot);
|
||||
nms.PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME.set(nmsPacket, packet.objectiveName);
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws Exception {
|
||||
String displayName = packet.displayName;
|
||||
if (clientVersion.getMinorVersion() < 13) {
|
||||
displayName = cutTo(displayName, 32);
|
||||
}
|
||||
Object nmsPacket = nms.newPacketPlayOutScoreboardObjective.newInstance();
|
||||
nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME.set(nmsPacket, packet.objectiveName);
|
||||
if (nms.minorVersion >= 13) {
|
||||
nms.PacketPlayOutScoreboardObjective_DISPLAYNAME.set(nmsPacket, toNMSComponent(IChatBaseComponent.optimizedComponent(displayName), clientVersion));
|
||||
} else {
|
||||
nms.PacketPlayOutScoreboardObjective_DISPLAYNAME.set(nmsPacket, displayName);
|
||||
}
|
||||
if (nms.PacketPlayOutScoreboardObjective_RENDERTYPE != null && packet.renderType != null) {
|
||||
if (nms.minorVersion >= 8) {
|
||||
nms.PacketPlayOutScoreboardObjective_RENDERTYPE.set(nmsPacket, Enum.valueOf(nms.EnumScoreboardHealthDisplay, packet.renderType.toString()));
|
||||
} else {
|
||||
nms.PacketPlayOutScoreboardObjective_RENDERTYPE.set(nmsPacket, packet.renderType.ordinal());
|
||||
}
|
||||
}
|
||||
nms.PacketPlayOutScoreboardObjective_METHOD.set(nmsPacket, packet.method);
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws Exception {
|
||||
if (nms.minorVersion >= 13) {
|
||||
return nms.newPacketPlayOutScoreboardScore_1_13.newInstance(Enum.valueOf(nms.EnumScoreboardAction, packet.action.toString()), packet.objectiveName, packet.player, packet.score);
|
||||
}
|
||||
if (packet.action == PacketPlayOutScoreboardScore.Action.REMOVE) {
|
||||
return nms.newPacketPlayOutScoreboardScore_String.newInstance(packet.player);
|
||||
}
|
||||
Object nmsPacket = nms.newPacketPlayOutScoreboardScore0.newInstance();
|
||||
nms.PacketPlayOutScoreboardScore_PLAYER.set(nmsPacket, packet.player);
|
||||
nms.PacketPlayOutScoreboardScore_OBJECTIVENAME.set(nmsPacket, packet.objectiveName);
|
||||
nms.PacketPlayOutScoreboardScore_SCORE.set(nmsPacket, packet.score);
|
||||
if (nms.minorVersion >= 8) {
|
||||
nms.PacketPlayOutScoreboardScore_ACTION.set(nmsPacket, Enum.valueOf(nms.EnumScoreboardAction, packet.action.toString()));
|
||||
} else {
|
||||
nms.PacketPlayOutScoreboardScore_ACTION.set(nmsPacket, packet.action.ordinal());
|
||||
}
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws Exception {
|
||||
String prefix = packet.playerPrefix;
|
||||
String suffix = packet.playerSuffix;
|
||||
if (clientVersion.getMinorVersion() < 13) {
|
||||
prefix = cutTo(prefix, 16);
|
||||
suffix = cutTo(suffix, 16);
|
||||
}
|
||||
Object nmsPacket = nms.newPacketPlayOutScoreboardTeam.newInstance();
|
||||
nms.PacketPlayOutScoreboardTeam_NAME.set(nmsPacket, packet.name);
|
||||
if (nms.minorVersion >= 13) {
|
||||
nms.PacketPlayOutScoreboardTeam_DISPLAYNAME.set(nmsPacket, toNMSComponent(IChatBaseComponent.optimizedComponent(packet.name), clientVersion));
|
||||
if (prefix != null && prefix.length() > 0) nms.PacketPlayOutScoreboardTeam_PREFIX.set(nmsPacket, toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
|
||||
if (suffix != null && suffix.length() > 0) nms.PacketPlayOutScoreboardTeam_SUFFIX.set(nmsPacket, toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
|
||||
EnumChatFormat format = packet.color != null ? packet.color : EnumChatFormat.lastColorsOf(prefix);
|
||||
nms.PacketPlayOutScoreboardTeam_CHATFORMAT.set(nmsPacket, Enum.valueOf(nms.EnumChatFormat, format.toString()));
|
||||
} else {
|
||||
nms.PacketPlayOutScoreboardTeam_DISPLAYNAME.set(nmsPacket, packet.name);
|
||||
nms.PacketPlayOutScoreboardTeam_PREFIX.set(nmsPacket, prefix);
|
||||
nms.PacketPlayOutScoreboardTeam_SUFFIX.set(nmsPacket, suffix);
|
||||
}
|
||||
if (nms.PacketPlayOutScoreboardTeam_VISIBILITY != null) nms.PacketPlayOutScoreboardTeam_VISIBILITY.set(nmsPacket, packet.nametagVisibility);
|
||||
if (nms.PacketPlayOutScoreboardTeam_COLLISION != null) nms.PacketPlayOutScoreboardTeam_COLLISION.set(nmsPacket, packet.collisionRule);
|
||||
nms.PacketPlayOutScoreboardTeam_PLAYERS.set(nmsPacket, packet.players);
|
||||
nms.PacketPlayOutScoreboardTeam_ACTION.set(nmsPacket, packet.method);
|
||||
nms.PacketPlayOutScoreboardTeam_SIGNATURE.set(nmsPacket, packet.options);
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutTitle packet, ProtocolVersion clientVersion) throws Exception {
|
||||
if (nms.minorVersion < 8) return null;
|
||||
return nms.newPacketPlayOutTitle.newInstance(Enum.valueOf(nms.EnumTitleAction, packet.action.toString()),
|
||||
packet.text == null ? null : toNMSComponent(IChatBaseComponent.optimizedComponent(packet.text), clientVersion), packet.fadeIn, packet.stay, packet.fadeOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity destroy packet with given parameter
|
||||
* @param ids - entity ids to destroy
|
||||
* @return destroy packet
|
||||
* @throws Exception - if reflection fails
|
||||
*/
|
||||
public Object buildEntityDestroyPacket(int... ids) throws Exception {
|
||||
return nms.newPacketPlayOutEntityDestroy.newInstance(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity metadata packet with given parameters
|
||||
* @param entityId - entity id
|
||||
* @param dataWatcher - datawatcher
|
||||
* @return metadata packet
|
||||
* @throws Exception - if reflection fails
|
||||
*/
|
||||
public Object buildEntityMetadataPacket(int entityId, DataWatcher dataWatcher) throws Exception {
|
||||
return nms.newPacketPlayOutEntityMetadata.newInstance(entityId, dataWatcher.toNMS(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity spawn packet with given parameters
|
||||
* @param entityId - entity id
|
||||
* @param uuid - entity uuid
|
||||
* @param entityType - entity type
|
||||
* @param loc - location to spawn at
|
||||
* @param dataWatcher - datawatcher
|
||||
* @return entity spawn packet
|
||||
* @throws Exception - if reflection fails
|
||||
*/
|
||||
public Object buildEntitySpawnPacket(int entityId, UUID uuid, EntityType entityType, Location loc, DataWatcher dataWatcher) throws Exception {
|
||||
Object nmsPacket = nms.newPacketPlayOutSpawnEntityLiving.newInstance();
|
||||
nms.PacketPlayOutSpawnEntityLiving_ENTITYID.set(nmsPacket, entityId);
|
||||
nms.PacketPlayOutSpawnEntityLiving_ENTITYTYPE.set(nmsPacket, entityIds.get(entityType));
|
||||
nms.PacketPlayOutSpawnEntityLiving_YAW.set(nmsPacket, (byte)(loc.getYaw() * 256.0f / 360.0f));
|
||||
nms.PacketPlayOutSpawnEntityLiving_PITCH.set(nmsPacket, (byte)(loc.getPitch() * 256.0f / 360.0f));
|
||||
if (nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER != null) {
|
||||
nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER.set(nmsPacket, dataWatcher.toNMS());
|
||||
}
|
||||
if (nms.minorVersion >= 9) {
|
||||
nms.PacketPlayOutSpawnEntityLiving_UUID.set(nmsPacket, uuid);
|
||||
nms.PacketPlayOutSpawnEntityLiving_X.set(nmsPacket, loc.getX());
|
||||
nms.PacketPlayOutSpawnEntityLiving_Y.set(nmsPacket, loc.getY());
|
||||
nms.PacketPlayOutSpawnEntityLiving_Z.set(nmsPacket, loc.getZ());
|
||||
} else {
|
||||
nms.PacketPlayOutSpawnEntityLiving_X.set(nmsPacket, floor((double)loc.getX()*32));
|
||||
nms.PacketPlayOutSpawnEntityLiving_Y.set(nmsPacket, floor((double)loc.getY()*32));
|
||||
nms.PacketPlayOutSpawnEntityLiving_Z.set(nmsPacket, floor((double)loc.getZ()*32));
|
||||
}
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity teleport packet with given parameters
|
||||
* @param entityId - entity id
|
||||
* @param location - location to teleport to
|
||||
* @return entity teleport packet
|
||||
* @throws Exception - if reflection fails
|
||||
*/
|
||||
public Object buildEntityTeleportPacket(int entityId, Location location) throws Exception {
|
||||
Object nmsPacket = nms.newPacketPlayOutEntityTeleport.newInstance();
|
||||
nms.PacketPlayOutEntityTeleport_ENTITYID.set(nmsPacket, entityId);
|
||||
if (nms.minorVersion >= 9) {
|
||||
nms.PacketPlayOutEntityTeleport_X.set(nmsPacket, location.getX());
|
||||
nms.PacketPlayOutEntityTeleport_Y.set(nmsPacket, location.getY());
|
||||
nms.PacketPlayOutEntityTeleport_Z.set(nmsPacket, location.getZ());
|
||||
} else {
|
||||
nms.PacketPlayOutEntityTeleport_X.set(nmsPacket, floor((double)location.getX()*32));
|
||||
nms.PacketPlayOutEntityTeleport_Y.set(nmsPacket, floor((double)location.getY()*32));
|
||||
nms.PacketPlayOutEntityTeleport_Z.set(nmsPacket, floor((double)location.getZ()*32));
|
||||
}
|
||||
nms.PacketPlayOutEntityTeleport_YAW.set(nmsPacket, (byte)((float)location.getYaw()/360*256));
|
||||
nms.PacketPlayOutEntityTeleport_PITCH.set(nmsPacket, (byte)((float)location.getPitch()/360*256));
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws Exception {
|
||||
if (nms.minorVersion < 8) return null;
|
||||
EnumPlayerInfoAction action = EnumPlayerInfoAction.valueOf(nms.PacketPlayOutPlayerInfo_ACTION.get(nmsPacket).toString());
|
||||
List<PlayerInfoData> listData = new ArrayList<PlayerInfoData>();
|
||||
for (Object nmsData : (List<?>) nms.PacketPlayOutPlayerInfo_PLAYERS.get(nmsPacket)) {
|
||||
Object nmsGamemode = nms.PlayerInfoData_GAMEMODE.get(nmsData);
|
||||
EnumGamemode gamemode = nmsGamemode == null ? null : EnumGamemode.valueOf(nmsGamemode.toString());
|
||||
Object profile = nms.PlayerInfoData_PROFILE.get(nmsData);
|
||||
Object nmsComponent = nms.PlayerInfoData_LISTNAME.get(nmsData);
|
||||
IChatBaseComponent listName = fromNMSComponent(nmsComponent);
|
||||
listData.add(new PlayerInfoData((String) nms.GameProfile_NAME.get(profile), (UUID) nms.GameProfile_ID.get(profile), nms.GameProfile_PROPERTIES.get(profile), nms.PlayerInfoData_PING.getInt(nmsData), gamemode, listName));
|
||||
}
|
||||
return new PacketPlayOutPlayerInfo(action, listData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket, ProtocolVersion clientVersion) throws Exception {
|
||||
String objective = (String) nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME.get(nmsPacket);
|
||||
String displayName;
|
||||
if (nms.minorVersion >= 13) {
|
||||
Object component = nms.PacketPlayOutScoreboardObjective_DISPLAYNAME.get(nmsPacket);
|
||||
IChatBaseComponent c = component == null ? null : fromNMSComponent(component);
|
||||
displayName = c == null ? null : c.toLegacyText();
|
||||
} else {
|
||||
displayName = (String) nms.PacketPlayOutScoreboardObjective_DISPLAYNAME.get(nmsPacket);
|
||||
}
|
||||
EnumScoreboardHealthDisplay renderType = null;
|
||||
if (nms.PacketPlayOutScoreboardObjective_RENDERTYPE != null) {
|
||||
Object nmsRender = nms.PacketPlayOutScoreboardObjective_RENDERTYPE.get(nmsPacket);
|
||||
if (nmsRender != null) {
|
||||
if (nms.minorVersion >= 8) {
|
||||
renderType = EnumScoreboardHealthDisplay.valueOf(nmsRender.toString());
|
||||
} else {
|
||||
renderType = EnumScoreboardHealthDisplay.values()[(int)nmsRender];
|
||||
}
|
||||
}
|
||||
}
|
||||
int method = nms.PacketPlayOutScoreboardObjective_METHOD.getInt(nmsPacket);
|
||||
return new PacketPlayOutScoreboardObjective(method, objective, displayName, renderType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket, ProtocolVersion clientVersion) throws Exception {
|
||||
return new PacketPlayOutScoreboardDisplayObjective(
|
||||
nms.PacketPlayOutScoreboardDisplayObjective_POSITION.getInt(nmsPacket),
|
||||
(String) nms.PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME.get(nmsPacket)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A method yoinked from minecraft code used to convert double to int
|
||||
* @param paramDouble double value
|
||||
* @return int value
|
||||
*/
|
||||
private int floor(double paramDouble){
|
||||
int i = (int)paramDouble;
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts json string into a component
|
||||
* @param json json as string
|
||||
* @return NMS component
|
||||
* @throws Exception if something fails
|
||||
*/
|
||||
public Object stringToComponent(String json) throws Exception {
|
||||
if (json == null) return null;
|
||||
return nms.ChatSerializer_DESERIALIZE.invoke(null, json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts minecraft IChatBaseComponent into TAB's component class. Currently does not support hover event.
|
||||
* @param component - component to convert
|
||||
* @return converted component
|
||||
* @throws Exception if something fails
|
||||
*/
|
||||
public IChatBaseComponent fromNMSComponent(Object component) throws Exception {
|
||||
long time = System.nanoTime();
|
||||
IChatBaseComponent obj = fromNMSComponent0(component);
|
||||
TAB.getInstance().getCPUManager().addMethodTime("fromNMSComponent", System.nanoTime()-time);
|
||||
return obj;
|
||||
}
|
||||
|
||||
//separate method to prevent extras counting cpu again due to recursion and finally showing higher usage than real
|
||||
@SuppressWarnings("rawtypes")
|
||||
public IChatBaseComponent fromNMSComponent0(Object component) throws Exception {
|
||||
if (!nms.ChatComponentText.isInstance(component)) return null; //paper
|
||||
IChatBaseComponent chat = new IChatBaseComponent((String) nms.ChatComponentText_text.get(component));
|
||||
Object modifier = nms.ChatBaseComponent_modifier.get(component);
|
||||
if (modifier != null) {
|
||||
Object color = nms.ChatModifier_color.get(modifier);
|
||||
if (color != null) {
|
||||
if (nms.minorVersion >= 16) {
|
||||
String name = (String) nms.ChatHexColor_name.get(color);
|
||||
if (name != null) {
|
||||
//legacy code
|
||||
chat.setColor(new TextColor(EnumChatFormat.valueOf(name.toUpperCase())));
|
||||
} else {
|
||||
int rgb = (int) nms.ChatHexColor_rgb.get(color);
|
||||
chat.setColor(new TextColor((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF));
|
||||
}
|
||||
} else {
|
||||
chat.setColor(new TextColor(EnumChatFormat.valueOf(((Enum)color).name())));
|
||||
}
|
||||
}
|
||||
chat.setBold((Boolean) nms.ChatModifier_bold.get(modifier));
|
||||
chat.setItalic((Boolean) nms.ChatModifier_italic.get(modifier));
|
||||
chat.setObfuscated((Boolean) nms.ChatModifier_obfuscated.get(modifier));
|
||||
chat.setStrikethrough((Boolean) nms.ChatModifier_strikethrough.get(modifier));
|
||||
chat.setUnderlined((Boolean) nms.ChatModifier_underlined.get(modifier));
|
||||
Object clickEvent = nms.ChatModifier_clickEvent.get(modifier);
|
||||
if (clickEvent != null) {
|
||||
chat.onClick(ClickAction.valueOf(nms.ChatClickable_action.get(clickEvent).toString().toUpperCase()), (String) nms.ChatClickable_value.get(clickEvent));
|
||||
}
|
||||
/* Object hoverEvent = nms.ChatModifier_hoverEvent.get(modifier);
|
||||
if (hoverEvent != null) {
|
||||
chat.onHover(HoverAction.fromString(nms.ChatHoverable_action.get(hoverEvent).toString()), fromNMSComponent(nms.ChatHoverable_value.get(hoverEvent)));
|
||||
}*/
|
||||
}
|
||||
for (Object extra : (List<Object>) nms.ChatBaseComponent_extra.get(component)) {
|
||||
chat.addExtra(fromNMSComponent0(extra));
|
||||
}
|
||||
return chat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TAB's IChatBaseComponent into minecraft's component. Currently does not support hover event.
|
||||
* @param component - component to convert
|
||||
* @param clientVersion - client version used to decide RGB conversion
|
||||
* @return converted component
|
||||
* @throws Exception if something fails
|
||||
*/
|
||||
public Object toNMSComponent(IChatBaseComponent component, ProtocolVersion clientVersion) throws Exception {
|
||||
long time = System.nanoTime();
|
||||
Object obj = toNMSComponent0(component, clientVersion);
|
||||
TAB.getInstance().getCPUManager().addMethodTime("toNMSComponent", System.nanoTime()-time);
|
||||
return obj;
|
||||
}
|
||||
|
||||
//separate method to prevent extras counting cpu again due to recursion and finally showing higher usage than real
|
||||
private Object toNMSComponent0(IChatBaseComponent component, ProtocolVersion clientVersion) throws Exception {
|
||||
if (component == null) return null;
|
||||
Object chat = nms.newChatComponentText.newInstance(component.getText());
|
||||
Object modifier;
|
||||
if (nms.minorVersion >= 16) {
|
||||
Object color = null;
|
||||
if (component.getColor() != null) {
|
||||
if (clientVersion.getMinorVersion() >= 16) {
|
||||
color = nms.ChatHexColor_ofInt.invoke(null, (component.getColor().getRed() << 16) + (component.getColor().getGreen() << 8) + component.getColor().getBlue());
|
||||
} else {
|
||||
color = nms.ChatHexColor_ofString.invoke(null, component.getColor().getLegacyColor().toString().toLowerCase());
|
||||
}
|
||||
}
|
||||
modifier = nms.newChatModifier.newInstance(
|
||||
color,
|
||||
component.isBold() ? true : null,
|
||||
component.isItalic() ? true : null,
|
||||
component.isUnderlined() ? true : null,
|
||||
component.isStrikethrough() ? true : null,
|
||||
component.isObfuscated() ? true : null,
|
||||
component.getClickAction() == null ? null : nms.newChatClickable.newInstance(nms.EnumClickAction_a.invoke(null, component.getClickAction().toString().toLowerCase()), component.getClickValue().toString()),
|
||||
null, //component.getHoverAction() == null ? null : nms.newChatHoverable.newInstance(nms.EnumHoverAction_a.invoke(null, component.getHoverAction().toString().toLowerCase()), stringToComponent(component.getHoverValue().toString())),
|
||||
null, null
|
||||
);
|
||||
} else {
|
||||
modifier = nms.newChatModifier.newInstance();
|
||||
if (component.getClickAction() != null){
|
||||
nms.ChatModifier_clickEvent.set(modifier, nms.newChatClickable.newInstance(nms.EnumClickAction_a.invoke(null, component.getClickAction().toString().toLowerCase()), component.getClickValue().toString()));
|
||||
}
|
||||
/* if (component.getHoverAction() != null) {
|
||||
nms.ChatModifier_hoverEvent.set(modifier, nms.newChatHoverable.newInstance(nms.EnumHoverAction_a.invoke(null, (component.getHoverAction().toString().toLowerCase())), stringToComponent(component.getHoverValue().toString())));
|
||||
}*/
|
||||
if (component.getColor() != null) nms.ChatModifier_color.set(modifier, Enum.valueOf(nms.EnumChatFormat, component.getColor().getLegacyColor().toString()));
|
||||
if (component.isBold()) nms.ChatModifier_bold.set(modifier, true);
|
||||
if (component.isItalic()) nms.ChatModifier_italic.set(modifier, true);
|
||||
if (component.isUnderlined()) nms.ChatModifier_underlined.set(modifier, true);
|
||||
if (component.isStrikethrough()) nms.ChatModifier_strikethrough.set(modifier, true);
|
||||
if (component.isObfuscated()) nms.ChatModifier_obfuscated.set(modifier, true);
|
||||
}
|
||||
nms.ChatBaseComponent_modifier.set(chat, modifier);
|
||||
for (IChatBaseComponent extra : component.getExtra()) {
|
||||
nms.ChatComponentText_addSibling.invoke(chat, toNMSComponent0(extra, clientVersion));
|
||||
}
|
||||
return chat;
|
||||
}
|
||||
}
|
||||
@@ -1,139 +1,75 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.NoSuchElementException;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
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 io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.features.PipelineInjector;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class BukkitPipelineInjector extends PipelineInjector {
|
||||
/**
|
||||
* Pipeline injection for Bukkit 1.8+.
|
||||
*/
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
//handler to inject before
|
||||
private final String INJECT_POSITION = "packet_handler";
|
||||
|
||||
//nms storage
|
||||
private NMSStorage nms;
|
||||
/** Function for getting player's channel */
|
||||
private static FunctionWithException<TabPlayer, Channel> getChannel;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param tab - tab instance
|
||||
* @param nms - nms storage
|
||||
*/
|
||||
public BukkitPipelineInjector(TAB tab, NMSStorage nms){
|
||||
super(tab);
|
||||
this.nms = nms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(TabPlayer player) {
|
||||
if (!player.getChannel().pipeline().names().contains(INJECT_POSITION)) {
|
||||
//fake player or waterfall bug
|
||||
return;
|
||||
}
|
||||
uninject(player);
|
||||
try {
|
||||
player.getChannel().pipeline().addBefore(INJECT_POSITION, DECODER_NAME, new BukkitChannelDuplexHandler(player));
|
||||
} catch (NoSuchElementException | IllegalArgumentException e) {
|
||||
//idk how does this keep happening but whatever
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BukkitPipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninject(TabPlayer player) {
|
||||
try {
|
||||
if (player.getChannel().pipeline().names().contains(DECODER_NAME)) player.getChannel().pipeline().remove(DECODER_NAME);
|
||||
} catch (NoSuchElementException e) {
|
||||
//for whatever reason this rarely throws
|
||||
//java.util.NoSuchElementException: TABReader
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 {
|
||||
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 {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all real players from team if packet does not come from TAB and reports this to override log
|
||||
* @param packetPlayOutScoreboardTeam - team packet
|
||||
* @throws Exception - if reflection fails
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws Exception {
|
||||
long time = System.nanoTime();
|
||||
if (nms.PacketPlayOutScoreboardTeam_SIGNATURE.getInt(packetPlayOutScoreboardTeam) != 69) {
|
||||
Collection<String> players = (Collection<String>) nms.PacketPlayOutScoreboardTeam_PLAYERS.get(packetPlayOutScoreboardTeam);
|
||||
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
|
||||
Collection<String> newList = new ArrayList<String>();
|
||||
for (String entry : players) {
|
||||
TabPlayer p = tab.getPlayer(entry);
|
||||
if (p == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
if (tab.getFeatureManager().getNameTagFeature().isDisabledWorld(p.getWorldName()) || p.hasTeamHandlingPaused()) {
|
||||
newList.add(entry);
|
||||
} else {
|
||||
logTeamOverride((String) nms.PacketPlayOutScoreboardTeam_NAME.get(packetPlayOutScoreboardTeam), entry);
|
||||
}
|
||||
}
|
||||
nms.PacketPlayOutScoreboardTeam_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
|
||||
}
|
||||
tab.getCPUManager().addTime(TabFeature.NAMETAGS, UsageType.ANTI_OVERRIDE, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom channel duplex handler override
|
||||
*/
|
||||
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
|
||||
|
||||
//injected player
|
||||
private TabPlayer player;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player
|
||||
* @param player - player to inject
|
||||
*/
|
||||
public BukkitChannelDuplexHandler(TabPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
public void channelRead(ChannelHandlerContext context, Object packet) throws Exception {
|
||||
try {
|
||||
Object modifiedPacket = tab.getFeatureManager().onPacketReceive(player, packet);
|
||||
if (modifiedPacket != null) super.channelRead(context, modifiedPacket);
|
||||
} catch (Throwable e){
|
||||
tab.getErrorManager().printError("An error occurred when reading packets", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) throws Exception {
|
||||
try {
|
||||
if (nms.PacketPlayOutPlayerInfo.isInstance(packet)) {
|
||||
super.write(context, tab.getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
|
||||
return;
|
||||
}
|
||||
if (tab.getFeatureManager().getNameTagFeature() != null && antiOverrideTeams && nms.PacketPlayOutScoreboardTeam.isInstance(packet)) {
|
||||
modifyPlayers(packet);
|
||||
super.write(context, packet, channelPromise);
|
||||
return;
|
||||
}
|
||||
if (nms.PacketPlayOutScoreboardDisplayObjective.isInstance(packet) && antiOverrideObjectives && tab.getFeatureManager().onDisplayObjective(player, packet)) {
|
||||
return;
|
||||
}
|
||||
if (antiOverrideObjectives && nms.PacketPlayOutScoreboardObjective.isInstance(packet)) {
|
||||
tab.getFeatureManager().onObjective(player, packet);
|
||||
}
|
||||
tab.getFeatureManager().onPacketSend(player, packet);
|
||||
} catch (Throwable e){
|
||||
tab.getErrorManager().printError("An error occurred when reading packets", e);
|
||||
}
|
||||
super.write(context, packet, channelPromise);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return getChannel.apply(player);
|
||||
}
|
||||
}
|
||||
@@ -1,319 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManager;
|
||||
import me.neznamy.tab.shared.placeholders.Placeholder;
|
||||
import me.neznamy.tab.shared.placeholders.PlaceholderRegistry;
|
||||
import me.neznamy.tab.shared.placeholders.PlayerPlaceholder;
|
||||
import me.neznamy.tab.shared.placeholders.ServerPlaceholder;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
|
||||
/**
|
||||
* Bukkit registry to register bukkit-only placeholders
|
||||
*/
|
||||
public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
|
||||
|
||||
//formatter for 2 decimal places
|
||||
public final DecimalFormat decimal2 = ((DecimalFormat)NumberFormat.getNumberInstance(Locale.US));
|
||||
|
||||
//plugin instance
|
||||
private JavaPlugin plugin;
|
||||
|
||||
//vault economy
|
||||
private Economy economy;
|
||||
|
||||
//vault chat
|
||||
private Chat chat;
|
||||
|
||||
//placeholder registry buffer
|
||||
private List<Placeholder> placeholders;
|
||||
|
||||
//essentials methods
|
||||
private Method getUser;
|
||||
private Method getNickname;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
* @param plugin - plugin instance
|
||||
*/
|
||||
public BukkitPlaceholderRegistry(JavaPlugin plugin) {
|
||||
decimal2.applyPattern("#.##");
|
||||
this.plugin = plugin;
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<Economy> rspEconomy = Bukkit.getServicesManager().getRegistration(Economy.class);
|
||||
if (rspEconomy != null) economy = rspEconomy.getProvider();
|
||||
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
|
||||
if (rspChat != null) chat = rspChat.getProvider();
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Essentials")) {
|
||||
try {
|
||||
getUser = Class.forName("com.earth2me.essentials.Essentials").getMethod("getUser", Player.class);
|
||||
getNickname = Class.forName("com.earth2me.essentials.User").getMethod("getNickname");
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to load essentials methods", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Placeholder> registerPlaceholders() {
|
||||
placeholders = new ArrayList<Placeholder>();
|
||||
placeholders.add(new PlayerPlaceholder("%money%", 1000) {
|
||||
public String get(TabPlayer p) {
|
||||
if (economy != null) return decimal2.format(economy.getBalance((Player) p.getPlayer()));
|
||||
return "-";
|
||||
}
|
||||
});
|
||||
|
||||
placeholders.add(new PlayerPlaceholder("%displayname%", 500) {
|
||||
public String get(TabPlayer p) {
|
||||
return ((Player) p.getPlayer()).getDisplayName();
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%deaths%", 1000) {
|
||||
public String get(TabPlayer p) {
|
||||
return String.valueOf(((Player) p.getPlayer()).getStatistic(Statistic.DEATHS));
|
||||
}
|
||||
});
|
||||
if (NMSStorage.getInstance().minorVersion >= 6) {
|
||||
placeholders.add(new PlayerPlaceholder("%health%", 100) {
|
||||
public String get(TabPlayer p) {
|
||||
return String.valueOf((int) Math.ceil(((Player) p.getPlayer()).getHealth()));
|
||||
}
|
||||
});
|
||||
}
|
||||
placeholders.add(new ServerPlaceholder("%tps%", 1000) {
|
||||
public String get() {
|
||||
try {
|
||||
Object nmsServer = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
double value = ((double[]) nmsServer.getClass().getField("recentTps").get(nmsServer))[0];
|
||||
return decimal2.format(Math.min(20, value));
|
||||
} catch (Throwable t) {
|
||||
return "-1";
|
||||
}
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%canseeonline%", 2000) {
|
||||
public String get(TabPlayer p) {
|
||||
int var = 0;
|
||||
for (TabPlayer all : TAB.getInstance().getPlayers()){
|
||||
if (((Player) p.getPlayer()).canSee((Player) all.getPlayer())) var++;
|
||||
}
|
||||
return String.valueOf(var);
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%canseestaffonline%", 2000) {
|
||||
public String get(TabPlayer p) {
|
||||
int var = 0;
|
||||
for (TabPlayer all : TAB.getInstance().getPlayers()){
|
||||
if (all.isStaff() && ((Player) p.getPlayer()).canSee((Player) all.getPlayer())) var++;
|
||||
}
|
||||
return String.valueOf(var);
|
||||
}
|
||||
});
|
||||
registerAFKPlaceholder();
|
||||
registerVaultPlaceholders();
|
||||
registerPositionPlaceholders();
|
||||
registerEssentialsPlaceholders();
|
||||
registerSyncPlaceholders();
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers AFK placeholder
|
||||
*/
|
||||
private void registerAFKPlaceholder() {
|
||||
String noAfk = TAB.getInstance().getConfiguration().config.getString("placeholders.afk-no", "");
|
||||
String yesAfk = TAB.getInstance().getConfiguration().config.getString("placeholders.afk-yes", " &4*&4&lAFK&4*&r");
|
||||
placeholders.add(new PlayerPlaceholder("%afk%", 500) {
|
||||
public String get(TabPlayer p) {
|
||||
boolean afk = false;
|
||||
try {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Essentials")) {
|
||||
Object user = Bukkit.getPluginManager().getPlugin("Essentials").getClass().getMethod("getUser", Player.class).invoke(Bukkit.getPluginManager().getPlugin("Essentials"), p.getPlayer());
|
||||
afk = (boolean) user.getClass().getMethod("isAfk").invoke(user);
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("AFKPlus")) {
|
||||
Object AFKPlus = Bukkit.getPluginManager().getPlugin("AFKPlus");
|
||||
Object AFKPlusPlayer = AFKPlus.getClass().getMethod("getPlayer", UUID.class).invoke(AFKPlus, p.getUniqueId());
|
||||
afk = (boolean) AFKPlusPlayer.getClass().getMethod("isAFK").invoke(AFKPlusPlayer);
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("AntiAFKPlus")) {
|
||||
Object api = Class.forName("de.kinglol12345.AntiAFKPlus.api.AntiAFKPlusAPI").getDeclaredMethod("getAPI").invoke(null);
|
||||
afk = (boolean) api.getClass().getMethod("isAFK", Player.class).invoke(api, p.getPlayer());
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("xAntiAFK")) {
|
||||
afk = (boolean) Class.forName("ch.soolz.xantiafk.xAntiAFKAPI").getMethod("isAfk", Player.class).invoke(null, p.getPlayer());
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("AutoAFK")) {
|
||||
Object plugin = Bukkit.getPluginManager().getPlugin("AutoAFK");
|
||||
Field f = plugin.getClass().getDeclaredField("afkList");
|
||||
f.setAccessible(true);
|
||||
HashMap<?, ?> map = (HashMap<?, ?>) f.get(plugin);
|
||||
afk = map.containsKey(p.getPlayer());
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
return TAB.getInstance().getErrorManager().printError("", "Failed to check AFK status of " + p.getName(), t);
|
||||
}
|
||||
if (!afk) {
|
||||
try {
|
||||
//purpur AFK API
|
||||
afk = (boolean) p.getPlayer().getClass().getMethod("isAfk").invoke(p.getPlayer());
|
||||
} catch (Throwable t) {
|
||||
//not purpur
|
||||
}
|
||||
}
|
||||
return afk ? yesAfk : noAfk;
|
||||
}
|
||||
@Override
|
||||
public String[] getNestedStrings(){
|
||||
return new String[] {yesAfk, noAfk};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers vault placeholders
|
||||
*/
|
||||
private void registerVaultPlaceholders() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault") && chat != null) {
|
||||
placeholders.add(new PlayerPlaceholder("%vault-prefix%", 500) {
|
||||
|
||||
public String get(TabPlayer p) {
|
||||
try {
|
||||
String prefix = chat.getPlayerPrefix((Player) p.getPlayer());
|
||||
return prefix != null ? prefix : "";
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError("", "Placeholder %vault-prefix% threw an exception", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%vault-suffix%", 500) {
|
||||
|
||||
public String get(TabPlayer p) {
|
||||
try {
|
||||
String suffix = chat.getPlayerSuffix((Player) p.getPlayer());
|
||||
return suffix != null ? suffix : "";
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError("", "Placeholder %vault-suffix% threw an exception", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
placeholders.add(new ServerPlaceholder("%vault-prefix%", 1000000) {
|
||||
public String get() {
|
||||
return "";
|
||||
}
|
||||
});
|
||||
placeholders.add(new ServerPlaceholder("%vault-suffix%", 1000000) {
|
||||
public String get() {
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers position placeholders
|
||||
*/
|
||||
private void registerPositionPlaceholders() {
|
||||
placeholders.add(new PlayerPlaceholder("%xPos%", 100) {
|
||||
public String get(TabPlayer p) {
|
||||
return String.valueOf(((Player) p.getPlayer()).getLocation().getBlockX());
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%yPos%", 100) {
|
||||
public String get(TabPlayer p) {
|
||||
return String.valueOf(((Player) p.getPlayer()).getLocation().getBlockY());
|
||||
}
|
||||
});
|
||||
placeholders.add(new PlayerPlaceholder("%zPos%", 100) {
|
||||
public String get(TabPlayer p) {
|
||||
return String.valueOf(((Player) p.getPlayer()).getLocation().getBlockZ());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers essentials placeholders
|
||||
*/
|
||||
private void registerEssentialsPlaceholders() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Essentials")) {
|
||||
placeholders.add(new PlayerPlaceholder("%essentialsnick%", 1000) {
|
||||
public String get(TabPlayer p) {
|
||||
String name = null;
|
||||
try {
|
||||
name = (String) getNickname.invoke(getUser.invoke(Bukkit.getPluginManager().getPlugin("Essentials"), p.getPlayer()));
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get Essentials nickname of " + p.getName(), e);
|
||||
}
|
||||
if (name == null || name.length() == 0) return p.getName();
|
||||
return TAB.getInstance().getConfiguration().essentialsNickPrefix + name;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
placeholders.add(new PlayerPlaceholder("%essentialsnick%", 100000000) {
|
||||
public String get(TabPlayer p) {
|
||||
return p.getName();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers synchronous placeholders
|
||||
*/
|
||||
private void registerSyncPlaceholders() {
|
||||
PlaceholderManager pl = TAB.getInstance().getPlaceholderManager();
|
||||
for (String identifier : pl.allUsedPlaceholderIdentifiers) {
|
||||
if (identifier.startsWith("%sync:")) {
|
||||
int refresh;
|
||||
if (pl.serverPlaceholderRefreshIntervals.containsKey(identifier)) {
|
||||
refresh = pl.serverPlaceholderRefreshIntervals.get(identifier);
|
||||
} else if (pl.playerPlaceholderRefreshIntervals.containsKey(identifier)) {
|
||||
refresh = pl.playerPlaceholderRefreshIntervals.get(identifier);
|
||||
} else {
|
||||
refresh = pl.defaultRefresh;
|
||||
}
|
||||
placeholders.add(new PlayerPlaceholder(identifier, refresh) {
|
||||
|
||||
@Override
|
||||
public String get(TabPlayer p) {
|
||||
Bukkit.getScheduler().runTask(plugin, new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long time = System.nanoTime();
|
||||
String syncedPlaceholder = identifier.substring(6, identifier.length()-1);
|
||||
String value = ((BukkitPlatform) TAB.getInstance().getPlatform()).setPlaceholders((Player) p.getPlayer(), "%" + syncedPlaceholder + "%");
|
||||
lastValue.put(p.getName(), value);
|
||||
if (!forceUpdate.contains(p.getName())) forceUpdate.add(p.getName());
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(getIdentifier(), System.nanoTime()-time);
|
||||
}
|
||||
});
|
||||
return lastValue.get(p.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.event.BukkitTABLoadEvent;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BossBar_legacy;
|
||||
import me.neznamy.tab.platforms.bukkit.features.ExpansionDownloader;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerlist;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PetFix;
|
||||
import me.neznamy.tab.platforms.bukkit.features.TabExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.features.unlimitedtags.NameTagX;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.permission.Vault;
|
||||
import me.neznamy.tab.shared.Platform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.BelowName;
|
||||
import me.neznamy.tab.shared.features.NameTag;
|
||||
import me.neznamy.tab.shared.features.PingSpoof;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManager;
|
||||
import me.neznamy.tab.shared.features.SpectatorFix;
|
||||
import me.neznamy.tab.shared.features.TabObjective;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManager;
|
||||
import me.neznamy.tab.shared.permission.LuckPerms;
|
||||
import me.neznamy.tab.shared.permission.None;
|
||||
import me.neznamy.tab.shared.permission.PermissionPlugin;
|
||||
import me.neznamy.tab.shared.permission.UltraPermissions;
|
||||
import me.neznamy.tab.shared.placeholders.Placeholder;
|
||||
import me.neznamy.tab.shared.placeholders.PlayerPlaceholder;
|
||||
import me.neznamy.tab.shared.placeholders.RelationalPlaceholder;
|
||||
import me.neznamy.tab.shared.placeholders.ServerPlaceholder;
|
||||
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
|
||||
/**
|
||||
* Bukkit implementation of Platform
|
||||
*/
|
||||
public class BukkitPlatform implements Platform {
|
||||
|
||||
//list of used expansions
|
||||
private Set<String> usedExpansions;
|
||||
|
||||
//plugin instance
|
||||
private JavaPlugin plugin;
|
||||
|
||||
//nms storage
|
||||
private NMSStorage nms;
|
||||
|
||||
//booleans to check plugin presence
|
||||
public boolean placeholderAPI;
|
||||
public boolean viaversion;
|
||||
public boolean idisguise;
|
||||
public boolean libsdisguises;
|
||||
public boolean essentials;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param plugin - plugin instance
|
||||
* @param nms - nms storage
|
||||
*/
|
||||
public BukkitPlatform(JavaPlugin plugin, NMSStorage nms) {
|
||||
this.plugin = plugin;
|
||||
this.nms = nms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionPlugin detectPermissionPlugin() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
|
||||
return new LuckPerms(Bukkit.getPluginManager().getPlugin("LuckPerms").getDescription().getVersion());
|
||||
} else if (Bukkit.getPluginManager().isPluginEnabled("UltraPermissions")) {
|
||||
return new UltraPermissions(Bukkit.getPluginManager().getPlugin("UltraPermissions").getDescription().getVersion());
|
||||
} else if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
return new Vault(Bukkit.getServicesManager().getRegistration(Permission.class).getProvider(), Bukkit.getPluginManager().getPlugin("Vault").getDescription().getVersion());
|
||||
} else {
|
||||
return new None();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFeatures() throws Exception {
|
||||
placeholderAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
|
||||
viaversion = Bukkit.getPluginManager().isPluginEnabled("ViaVersion");
|
||||
idisguise = Bukkit.getPluginManager().isPluginEnabled("iDisguise");
|
||||
libsdisguises = Bukkit.getPluginManager().isPluginEnabled("LibsDisguises");
|
||||
essentials = Bukkit.getPluginManager().isPluginEnabled("Essentials");
|
||||
TAB tab = TAB.getInstance();
|
||||
usedExpansions = new HashSet<String>();
|
||||
tab.getPlaceholderManager().addRegistry(new BukkitPlaceholderRegistry(plugin));
|
||||
tab.getPlaceholderManager().addRegistry(new UniversalPlaceholderRegistry());
|
||||
tab.getPlaceholderManager().registerPlaceholders();
|
||||
if (nms.minorVersion >= 8 && tab.getConfiguration().pipelineInjection) {
|
||||
tab.getFeatureManager().registerFeature("injection", new BukkitPipelineInjector(tab, nms));
|
||||
}
|
||||
loadNametagFeature(tab);
|
||||
loadUniversalFeatures();
|
||||
if (tab.getConfiguration().config.getBoolean("ping-spoof.enabled", false)) tab.getFeatureManager().registerFeature("pingspoof", new PingSpoof());
|
||||
if (tab.getConfiguration().config.getString("yellow-number-in-tablist", "%ping%").length() > 0) tab.getFeatureManager().registerFeature("tabobjective", new TabObjective(tab));
|
||||
if (tab.getConfiguration().config.getBoolean("do-not-move-spectators", false)) tab.getFeatureManager().registerFeature("spectatorfix", new SpectatorFix());
|
||||
if (tab.getConfiguration().config.getBoolean("classic-vanilla-belowname.enabled", true)) tab.getFeatureManager().registerFeature("belowname", new BelowName(tab));
|
||||
if (tab.getConfiguration().premiumconfig != null && tab.getConfiguration().premiumconfig.getBoolean("scoreboard.enabled", false)) tab.getFeatureManager().registerFeature("scoreboard", new ScoreboardManager(tab));
|
||||
if (tab.getConfiguration().bossbar.getBoolean("bossbar-enabled", false) && nms.minorVersion < 9) tab.getFeatureManager().registerFeature("bossbar1.8", new BossBar_legacy(tab, plugin));
|
||||
if (nms.minorVersion >= 9 && tab.getConfiguration().config.getBoolean("fix-pet-names", false)) tab.getFeatureManager().registerFeature("petfix", new PetFix(nms));
|
||||
if (tab.getConfiguration().config.getBoolean("per-world-playerlist.enabled", false)) tab.getFeatureManager().registerFeature("pwp", new PerWorldPlayerlist(plugin, tab));
|
||||
if (placeholderAPI) {
|
||||
new TabExpansion(plugin);
|
||||
new ExpansionDownloader(plugin).download(usedExpansions);
|
||||
}
|
||||
|
||||
for (Player p : getOnlinePlayers()) {
|
||||
tab.addPlayer(new BukkitTabPlayer(p));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads nametag feature from config
|
||||
* @param tab - tab instance
|
||||
*/
|
||||
private void loadNametagFeature(TAB tab) {
|
||||
if (tab.getConfiguration().config.getBoolean("change-nametag-prefix-suffix", true)) {
|
||||
if (tab.getConfiguration().config.getBoolean("unlimited-nametag-prefix-suffix-mode.enabled", false) && nms.minorVersion >= 8) {
|
||||
tab.getFeatureManager().registerFeature("nametagx", new NameTagX(plugin, nms, tab));
|
||||
} else {
|
||||
tab.getFeatureManager().registerFeature("nametag16", new NameTag(tab));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of online players from Bukkit API
|
||||
* @return list of online players from Bukkit API
|
||||
* @throws Exception - if reflection fails
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Player[] getOnlinePlayers() throws Exception {
|
||||
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
|
||||
if (players instanceof Player[]) {
|
||||
//1.7.x
|
||||
return (Player[]) players;
|
||||
} else {
|
||||
//1.8+
|
||||
return ((Collection<Player>)players).toArray(new Player[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendConsoleMessage(String message, boolean translateColors) {
|
||||
Bukkit.getConsoleSender().sendMessage(translateColors ? message.replace('&', '\u00a7') : message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(String identifier) {
|
||||
if (identifier.contains("_")) {
|
||||
PlaceholderManager pl = TAB.getInstance().getPlaceholderManager();
|
||||
if (identifier.startsWith("%rel_")) {
|
||||
//relational placeholder
|
||||
registerRelationalPlaceholder(identifier, pl.getRelationalRefresh(identifier));
|
||||
} else {
|
||||
//normal placeholder
|
||||
String plugin = identifier.split("_")[0].substring(1).toLowerCase();
|
||||
if (plugin.equals("some")) return;
|
||||
usedExpansions.add(plugin);
|
||||
if (pl.serverPlaceholderRefreshIntervals.containsKey(identifier)) {
|
||||
registerServerPlaceholder(identifier, pl.serverPlaceholderRefreshIntervals.get(identifier));
|
||||
return;
|
||||
}
|
||||
if (pl.playerPlaceholderRefreshIntervals.containsKey(identifier)) {
|
||||
registerPlayerPlaceholder(identifier, pl.playerPlaceholderRefreshIntervals.get(identifier));
|
||||
return;
|
||||
}
|
||||
registerPlayerPlaceholder(identifier, pl.defaultRefresh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers server placeholder
|
||||
* @param identifier - placeholder identifier
|
||||
* @param refresh - refresh interval in milliseconds
|
||||
*/
|
||||
private void registerServerPlaceholder(String identifier, int refresh) {
|
||||
TAB.getInstance().getPlaceholderManager().registerPlaceholder(new ServerPlaceholder(identifier, TAB.getInstance().getErrorManager().fixPlaceholderInterval(identifier, refresh)){
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
return setPlaceholders(null, identifier);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers player placeholder
|
||||
* @param identifier - placeholder identifier
|
||||
* @param refresh - refresh interval in milliseconds
|
||||
*/
|
||||
private void registerPlayerPlaceholder(String identifier, int refresh) {
|
||||
TAB.getInstance().getPlaceholderManager().registerPlaceholder(new PlayerPlaceholder(identifier, TAB.getInstance().getErrorManager().fixPlaceholderInterval(identifier, refresh)) {
|
||||
|
||||
@Override
|
||||
public String get(TabPlayer p) {
|
||||
return setPlaceholders((Player) p.getPlayer(), identifier);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers relational placeholder
|
||||
* @param identifier - placeholder identifier
|
||||
* @param refresh - refresh interval in milliseconds
|
||||
*/
|
||||
private void registerRelationalPlaceholder(String identifier, int refresh) {
|
||||
TAB.getInstance().getPlaceholderManager().registerPlaceholder(new RelationalPlaceholder(identifier, TAB.getInstance().getErrorManager().fixPlaceholderInterval(identifier, refresh)) {
|
||||
|
||||
@Override
|
||||
public String get(TabPlayer viewer, TabPlayer target) {
|
||||
if (!placeholderAPI) return identifier;
|
||||
try {
|
||||
return PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier);
|
||||
} catch (Throwable t) {
|
||||
TAB.getInstance().getErrorManager().printError("PlaceholderAPI v" + Bukkit.getPluginManager().getPlugin("PlaceholderAPI").getDescription().getVersion() + " generated an error when setting relational placeholder " + identifier + " for viewer " + viewer.getName() + " and target " + target.getName(), t, false, TAB.getInstance().getErrorManager().papiErrorLog);
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs PlaceholderAPI call and returns the output. If the task fails, error is logged and "ERROR" returned instead
|
||||
* @param player - player to set placeholder for
|
||||
* @param placeholder - placeholder
|
||||
* @return result from PlaceholderAPI
|
||||
*/
|
||||
public String setPlaceholders(Player player, String placeholder) {
|
||||
if (!placeholderAPI) return placeholder;
|
||||
try {
|
||||
return PlaceholderAPI.setPlaceholders(player, placeholder);
|
||||
} catch (Throwable t) {
|
||||
String playername = (player == null ? "<null>" : player.getName());
|
||||
TAB.getInstance().getErrorManager().printError("PlaceholderAPI v" + Bukkit.getPluginManager().getPlugin("PlaceholderAPI").getDescription().getVersion() + " generated an error when setting placeholder " + placeholder + " for player " + playername, t, false, TAB.getInstance().getErrorManager().papiErrorLog);
|
||||
return "ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerVersion() {
|
||||
return Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSeparatorType() {
|
||||
return "world";
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String replaceAllPlaceholders(String string, TabPlayer sender) {
|
||||
if (string == null) return null;
|
||||
String replaced = string;
|
||||
for (Placeholder p : TAB.getInstance().getPlaceholderManager().getAllPlaceholders()) {
|
||||
if (replaced.contains(p.getIdentifier())) {
|
||||
if (p instanceof ServerPlaceholder) {
|
||||
((ServerPlaceholder)p).update();
|
||||
}
|
||||
if (p instanceof PlayerPlaceholder) {
|
||||
((PlayerPlaceholder)p).update(sender);
|
||||
}
|
||||
replaced = p.set(replaced, sender);
|
||||
}
|
||||
}
|
||||
replaced = setPlaceholders(sender == null ? null : (Player) sender.getPlayer(), replaced);
|
||||
return replaced;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callLoadEvent() {
|
||||
Bukkit.getPluginManager().callEvent(new BukkitTABLoadEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxPlayers() {
|
||||
return Bukkit.getMaxPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigName() {
|
||||
return "bukkitconfig.yml";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* Main class for Bukkit.
|
||||
*/
|
||||
public class BukkitTAB extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
boolean folia = ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer");
|
||||
TAB.create(folia ? new FoliaPlatform(this) : new BukkitPlatform(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
TAB.getInstance().unload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Command handler for /tab command
|
||||
*/
|
||||
public class BukkitTabCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
|
||||
sender.sendMessage(EnumChatFormat.color(message));
|
||||
}
|
||||
} else {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof Player) {
|
||||
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
|
||||
if (p == null) return true; //player not loaded correctly
|
||||
}
|
||||
TAB.getInstance().getCommand().execute(p, args);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof Player) {
|
||||
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
|
||||
if (p == null) return Collections.emptyList(); //player not loaded correctly
|
||||
}
|
||||
return TAB.getInstance().getCommand().complete(p, args);
|
||||
}
|
||||
}
|
||||
@@ -1,220 +1,120 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Entity;
|
||||
import lombok.Getter;
|
||||
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.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.ITabPlayer;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* TabPlayer for Bukkit
|
||||
* TabPlayer implementation for Bukkit platform
|
||||
*/
|
||||
public class BukkitTabPlayer extends ITabPlayer {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Getter
|
||||
public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
//bukkit player
|
||||
private Player player;
|
||||
@NotNull
|
||||
private final Scoreboard<BukkitTabPlayer, ?> scoreboard = ScoreboardLoader.getInstance().apply(this);
|
||||
|
||||
//nms handle
|
||||
private Object handle;
|
||||
@NotNull
|
||||
private final TabListBase<?> tabList = TabListBase.getInstance().apply(this);
|
||||
|
||||
//nms player connection
|
||||
private Object playerConnection;
|
||||
@NotNull
|
||||
private final BossBar bossBar = BossBarLoader.findInstance(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
* @param p - bukkit player
|
||||
*/
|
||||
public BukkitTabPlayer(Player p){
|
||||
player = p;
|
||||
world = p.getWorld().getName();
|
||||
try {
|
||||
handle = NMSStorage.getInstance().getHandle.invoke(player);
|
||||
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get playerConnection of " + p.getName(), e);
|
||||
}
|
||||
try {
|
||||
if (NMSStorage.getInstance().CHANNEL != null)
|
||||
channel = (Channel) NMSStorage.getInstance().CHANNEL.get(NMSStorage.getInstance().NETWORK_MANAGER.get(playerConnection));
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + p.getName(), e);
|
||||
}
|
||||
uniqueId = p.getUniqueId();
|
||||
name = p.getName();
|
||||
version = ProtocolVersion.fromNetworkId(getProtocolVersion());
|
||||
init();
|
||||
}
|
||||
@NotNull
|
||||
private final EntityView entityView = PacketEntityView.isAvailable() ? new PacketEntityView(this) : new DummyEntityView();
|
||||
|
||||
/**
|
||||
* Gets protocol version and returns it
|
||||
* @return protocol version of this player
|
||||
*/
|
||||
private int getProtocolVersion() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport")){
|
||||
int version = getProtocolVersionPS();
|
||||
//some PS versions return -1 on unsupported server versions instead of throwing exception
|
||||
if (version != -1 && version < ProtocolVersion.SERVER_VERSION.getNetworkId()) return version;
|
||||
}
|
||||
if (((BukkitPlatform)TAB.getInstance().getPlatform()).viaversion) {
|
||||
return getProtocolVersionVia();
|
||||
}
|
||||
return ProtocolVersion.SERVER_VERSION.getNetworkId();
|
||||
}
|
||||
/**
|
||||
* Constructs new instance with given bukkit player
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param p
|
||||
* bukkit player
|
||||
*/
|
||||
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
|
||||
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName(), platform.getServerVersion().getNetworkId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns protocol version of this player using ProtocolSupport
|
||||
* @return protocol version of this player using ProtocolSupport
|
||||
*/
|
||||
private int getProtocolVersionPS(){
|
||||
try {
|
||||
Object protocolVersion = Class.forName("protocolsupport.api.ProtocolSupportAPI").getMethod("getProtocolVersion", Player.class).invoke(null, player);
|
||||
int version = (int) protocolVersion.getClass().getMethod("getId").invoke(protocolVersion);
|
||||
TAB.getInstance().debug("ProtocolSupport returned protocol version " + version + " for " + getName());
|
||||
return version;
|
||||
} catch (Throwable e) {
|
||||
return TAB.getInstance().getErrorManager().printError(ProtocolVersion.SERVER_VERSION.getNetworkId(), "Failed to get protocol version of " + getName() + " using ProtocolSupport", e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String permission) {
|
||||
return getPlayer().hasPermission(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns protocol version of this player using ViaVersion
|
||||
* @return protocol version of this player using ViaVersion
|
||||
*/
|
||||
private int getProtocolVersionVia(){
|
||||
try {
|
||||
int version = Via.getAPI().getPlayerVersion(uniqueId);
|
||||
TAB.getInstance().debug("ViaVersion returned protocol version " + version + " for " + getName());
|
||||
return version;
|
||||
} catch (Throwable e) {
|
||||
return TAB.getInstance().getErrorManager().printError(ProtocolVersion.SERVER_VERSION.getNetworkId(), "Failed to get protocol version of " + getName() + " using ViaVersion", e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public int getPing() {
|
||||
return PingRetriever.getPing(getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
return player.hasPermission(permission);
|
||||
}
|
||||
@Override
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendMessage(getPlatform().toBukkitFormat(message, getVersion().supportsRGB()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPing() {
|
||||
try {
|
||||
int ping = NMSStorage.getInstance().PING.getInt(handle);
|
||||
if (ping > 10000 || ping < 0) ping = -1;
|
||||
return ping;
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return getPlayer().hasPotionEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Object nmsPacket) {
|
||||
if (nmsPacket == null || !player.isOnline()) return;
|
||||
try {
|
||||
if (((BukkitPlatform)TAB.getInstance().getPlatform()).viaversion && nmsPacket instanceof ByteBuf) {
|
||||
try {
|
||||
Via.getAPI().sendRawPacket(uniqueId, (ByteBuf) nmsPacket);
|
||||
} catch (IllegalArgumentException e) {
|
||||
//java.lang.IllegalArgumentException: This player is not controlled by ViaVersion!
|
||||
//this is only used to send 1.9 bossbar packets on 1.8 servers, no idea why it does this sometimes
|
||||
}
|
||||
return;
|
||||
}
|
||||
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
|
||||
} catch (Throwable e) {
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
return LibsDisguisesHook.isDisguised(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return player.hasPotionEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
@Override
|
||||
@Nullable
|
||||
public TabList.Skin getSkin() {
|
||||
return tabList.getSkin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
return isDisguisedLD() || isDisguisediDis();
|
||||
}
|
||||
@Override
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
return (Player) player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns disguise status of player using LibsDisguises
|
||||
* @return disguise status of player using LibsDisguises
|
||||
*/
|
||||
private boolean isDisguisedLD() {
|
||||
try {
|
||||
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).libsdisguises) return false;
|
||||
return (boolean) Class.forName("me.libraryaddict.disguise.DisguiseAPI").getMethod("isDisguised", Entity.class).invoke(null, player);
|
||||
} catch (Throwable e) {
|
||||
return TAB.getInstance().getErrorManager().printError(false, "Failed to check disguise status using LibsDisguises", e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public BukkitPlatform getPlatform() {
|
||||
return (BukkitPlatform) platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns disguise status of player using iDisguise
|
||||
* @return disguise status of player using iDisguise
|
||||
*/
|
||||
private boolean isDisguisediDis() {
|
||||
try {
|
||||
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).idisguise) return false;
|
||||
RegisteredServiceProvider<?> provider = Bukkit.getServicesManager().getRegistration(Class.forName("de.robingrether.idisguise.api.DisguiseAPI"));
|
||||
Object iDisguise = provider.getProvider();
|
||||
Method m = iDisguise.getClass().getMethod("isDisguised", Player.class);
|
||||
m.setAccessible(true);
|
||||
return (boolean) m.invoke(iDisguise, player);
|
||||
} catch (Throwable e) {
|
||||
return TAB.getInstance().getErrorManager().printError(false, "Failed to check disguise status using iDisguise", e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
for (MetadataValue v : getPlayer().getMetadata("vanished")) {
|
||||
if (v.asBoolean()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSkin() {
|
||||
try {
|
||||
return Class.forName("com.mojang.authlib.GameProfile").getMethod("getProperties").invoke(NMSStorage.getInstance().getProfile.invoke(handle));
|
||||
} catch (Throwable e) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to get skin of " + getName(), e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().getGameMode().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
@Override
|
||||
public double getHealth() {
|
||||
return getPlayer().getHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return player.isOnline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
try {
|
||||
if (((BukkitPlatform)TAB.getInstance().getPlatform()).essentials) {
|
||||
Object essentials = Bukkit.getPluginManager().getPlugin("Essentials");
|
||||
Object user = essentials.getClass().getMethod("getUser", Player.class).invoke(essentials, player);
|
||||
boolean vanished = (boolean) user.getClass().getMethod("isVanished").invoke(user);
|
||||
if (vanished) return true;
|
||||
}
|
||||
if (player.hasMetadata("vanished") && !player.getMetadata("vanished").isEmpty()) {
|
||||
return player.getMetadata("vanished").get(0).asBoolean();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to check vanish status of " + player.getName(), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return player.getGameMode().getValue();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
@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() + ".");
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
|
||||
/**
|
||||
* Main class for Bukkit platform
|
||||
*/
|
||||
public class Main extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable(){
|
||||
ProtocolVersion.SERVER_VERSION = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a77[TAB] Server version: " + Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")");
|
||||
if (!isVersionSupported()){
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
if (ProtocolVersion.SERVER_VERSION == ProtocolVersion.UNKNOWN) {
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a7c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly.");
|
||||
}
|
||||
TAB.setInstance(new TAB(new BukkitPlatform(this, NMSStorage.getInstance()), new BukkitPacketBuilder(NMSStorage.getInstance())));
|
||||
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), this);
|
||||
Bukkit.getPluginCommand("tab").setExecutor((sender, c, cmd, args) -> {
|
||||
if (TAB.getInstance().isDisabled()) {
|
||||
for (String message : TAB.getInstance().disabledCommand.execute(args, sender.hasPermission("tab.reload"), sender.hasPermission("tab.admin"))) {
|
||||
sender.sendMessage(message.replace('&', '\u00a7'));
|
||||
}
|
||||
} else {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof Player) {
|
||||
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
|
||||
if (p == null) return false; //player not loaded correctly
|
||||
}
|
||||
TAB.getInstance().command.execute(p, args);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
Bukkit.getPluginCommand("tab").setTabCompleter((sender, c, cmd, args) -> {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof Player) {
|
||||
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
|
||||
if (p == null) return new ArrayList<String>(); //player not loaded correctly
|
||||
}
|
||||
return TAB.getInstance().command.complete(p, args);
|
||||
});
|
||||
TAB.getInstance().load();
|
||||
new BukkitMetrics(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
//null check due to L23 return making L30 not run
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all used NMS classes, constructors, fields and methods and returns true if everything went successfully and version is marked as compatible
|
||||
* @return true if compatible, false if not
|
||||
*/
|
||||
private boolean isVersionSupported(){
|
||||
List<String> SUPPORTED_VERSIONS = Arrays.asList(
|
||||
"v1_5_R1", "v1_5_R2", "v1_5_R3", "v1_6_R1", "v1_6_R2", "v1_6_R3",
|
||||
"v1_7_R1", "v1_7_R2", "v1_7_R3", "v1_7_R4", "v1_8_R1", "v1_8_R2", "v1_8_R3",
|
||||
"v1_9_R1", "v1_9_R2", "v1_10_R1", "v1_11_R1", "v1_12_R1", "v1_13_R1", "v1_13_R2",
|
||||
"v1_14_R1", "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3");
|
||||
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
try {
|
||||
NMSStorage.setInstance(new NMSStorage());
|
||||
if (SUPPORTED_VERSIONS.contains(serverPackage)) {
|
||||
return true;
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a7c[TAB] This plugin version does not claim to support your server version. This jar has only been tested on 1.7.x - 1.16.5. Disabling.");
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if (SUPPORTED_VERSIONS.contains(serverPackage)) {
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a7c[TAB] Your server version is marked as compatible, but a compatibility issue was found. Please report the error below (include your server version & fork too)");
|
||||
e.printStackTrace();
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a7c[TAB] Your server version is completely unsupported. This plugin version only supports 1.7.x - 1.16.5. Disabling.");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
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.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* BossBar for 1.9+ servers where Bukkit API is used. If ViaVersion is used
|
||||
* to allow 1.8 players, it will handle the entity and teleporting by itself.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitBossBar implements BossBar {
|
||||
|
||||
/** 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(
|
||||
player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()),
|
||||
org.bukkit.boss.BarColor.valueOf(color.name()),
|
||||
styles[style.ordinal()]
|
||||
);
|
||||
bar.setProgress(progress);
|
||||
bar.addPlayer(player.getPlayer());
|
||||
bossBars.put(id, bar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
bossBars.get(id).setTitle(player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
bossBars.get(id).setProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
bossBars.get(id).setStyle(styles[style.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
bossBars.remove(id).removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.entity.DataWatcher;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* BossBar using wither entity for <1.9 players on <1.9 servers.
|
||||
* Additional logic, such as teleporting the entity must be done
|
||||
* separately, as this class does not handle it.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class EntityBossBar implements BossBar {
|
||||
|
||||
/** 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 = WITHER_MAX_HEALTH*progress;
|
||||
if (health == 0) health = 1;
|
||||
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.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 = WITHER_MAX_HEALTH*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.setHealth(health);
|
||||
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {/*Added in 1.9*/}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {/*Added in 1.9*/}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
player.getEntityView().destroyEntities(id.hashCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.chat.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.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Handler for 1.9+ players on 1.8 server using ViaVersion API.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ViaBossBar implements BossBar {
|
||||
|
||||
/** 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(
|
||||
TabComponent.optimized(title).toLegacyText(),
|
||||
progress,
|
||||
BossColor.valueOf(color.name()),
|
||||
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(TabComponent.optimized(title).toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
viaBossBars.get(id).setHealth(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
viaBossBars.get(id).setStyle(styles[style.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
viaBossBars.remove(id).removePlayer(player.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -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,87 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitPacketBuilder;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
|
||||
/**
|
||||
* An additional class with additional code for <1.9 servers due to an entity being required
|
||||
*/
|
||||
public class BossBar_legacy implements Listener, Loadable {
|
||||
|
||||
//distance of wither in blocks
|
||||
private final int WITHER_DISTANCE = 75;
|
||||
|
||||
//main bossbar feature
|
||||
private BossBar mainFeature;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the class
|
||||
* @param mainFeature - main bossbar feature
|
||||
*/
|
||||
public BossBar_legacy(TAB tab, JavaPlugin plugin) {
|
||||
this.mainFeature = (BossBar) tab.getFeatureManager().getFeature("bossbar");
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
//bar disappears in client after ~1 second of not seeing boss entity
|
||||
tab.getCPUManager().startRepeatingMeasuredTask(900, "refreshing bossbar", TabFeature.BOSSBAR, UsageType.TELEPORTING_ENTITY, () -> teleport());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
teleport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates wither location for all players
|
||||
*/
|
||||
private void teleport() {
|
||||
for (TabPlayer all : TAB.getInstance().getPlayers()) {
|
||||
if (all.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
|
||||
for (me.neznamy.tab.api.bossbar.BossBar l : all.getActiveBossBars()) {
|
||||
try {
|
||||
all.sendPacket(((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityTeleportPacket(Math.abs(l.getUniqueId().hashCode()), getWitherLocation(all)), TabFeature.BOSSBAR);
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to create PacketPlayOutEntityTeleport", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning wither as respawn screen destroys all entities in client
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask("processing PlayerRespawnEvent", TabFeature.BOSSBAR, UsageType.PLAYER_RESPAWN_EVENT, () -> mainFeature.detectBossBarsAndSend(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns location where wither should be located based on where player is looking
|
||||
* @param p - player to get wither location for
|
||||
* @return location of wither
|
||||
*/
|
||||
public Location getWitherLocation(TabPlayer p) {
|
||||
Player pl = (Player) p.getPlayer();
|
||||
Location loc = pl.getEyeLocation().add(pl.getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
|
||||
if (loc.getY() < 1) loc.setY(1);
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
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.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Pose;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The core class for unlimited NameTag mode on Bukkit
|
||||
*/
|
||||
public class BukkitNameTagX extends BackendNameTagX implements Listener {
|
||||
|
||||
/** 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) {
|
||||
sneak(e.getPlayer().getUniqueId(), e.isSneaking());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to respawn event to resend armor stands.
|
||||
*
|
||||
* @param e
|
||||
* Respawn event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
respawn(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDistance(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
|
||||
Location loc1 = ((Player) player1.getPlayer()).getLocation();
|
||||
Location loc2 = ((Player) player2.getPlayer()).getLocation();
|
||||
return Math.sqrt(Math.pow(loc1.getX()-loc2.getX(), 2) + Math.pow(loc1.getZ()-loc2.getZ(), 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
|
||||
return ((Player) player1.getPlayer()).getWorld() == ((Player) player2.getPlayer()).getWorld();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
return ((Player)viewer.getPlayer()).canSee((Player) target.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListener() {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
@NotNull
|
||||
public List<Integer> getPassengers(@NotNull Object entity) {
|
||||
Entity vehicle = (Entity) entity;
|
||||
if (BukkitReflection.getMinorVersion() >= MULTI_PASSENGER_VERSION) {
|
||||
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
|
||||
} else {
|
||||
if (vehicle.getPassenger() != null) {
|
||||
return Collections.singletonList(vehicle.getPassenger().getEntityId());
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getVehicle(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getVehicle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEntityId(@NotNull Object entity) {
|
||||
return ((Entity)entity).getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getEntityType(@NotNull Object entity) {
|
||||
return ((Entity) entity).getType().toString().toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).isSneaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSwimming(@NotNull TabPlayer player) {
|
||||
Player p = (Player) player.getPlayer();
|
||||
if (BukkitReflection.getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
|
||||
return BukkitReflection.getMinorVersion() == 13 && p.isSwimming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGliding(@NotNull TabPlayer player) {
|
||||
return BukkitReflection.getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSleeping(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).isSleeping();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object getArmorStandType() {
|
||||
return EntityType.ARMOR_STAND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getX(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getLocation().getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getY(@NotNull Object entity) {
|
||||
return ((Entity)entity).getLocation().getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getZ(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getLocation().getZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
|
||||
DataWatcher datawatcher = new DataWatcher();
|
||||
datawatcher.setEntityFlags(flags);
|
||||
datawatcher.setCustomName(displayName, viewer.getVersion());
|
||||
datawatcher.setCustomNameVisible(nameVisible);
|
||||
datawatcher.setArmorStandFlags(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();
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TAB's expansion for PlaceholderAPI
|
||||
*/
|
||||
@Getter
|
||||
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
|
||||
|
||||
/** Map holding all values for all players for easy and high-performance access */
|
||||
@NotNull
|
||||
private final WeakHashMap<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
|
||||
|
||||
/** List of all placeholders offered by the plugin for command suggestions */
|
||||
@NotNull
|
||||
private final List<String> placeholders = 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() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getAuthor() {
|
||||
return TabConstants.PLUGIN_AUTHOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getIdentifier() {
|
||||
return TabConstants.PLUGIN_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getVersion() {
|
||||
return TabConstants.PLUGIN_VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<String> getPlaceholders() {
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String onPlaceholderRequest(@Nullable Player player, @NotNull String identifier) {
|
||||
if (identifier.startsWith("replace_")) {
|
||||
String text = "%" + identifier.substring(8) + "%";
|
||||
String textBefore;
|
||||
do {
|
||||
textBefore = text;
|
||||
for (String placeholder : TAB.getInstance().getPlaceholderManager().detectPlaceholders(text)) {
|
||||
text = text.replace(placeholder, TAB.getInstance().getPlaceholderManager().findReplacement(placeholder,
|
||||
PlaceholderAPI.setPlaceholders(player, placeholder)));
|
||||
}
|
||||
} while (!textBefore.equals(text));
|
||||
return text;
|
||||
}
|
||||
if (identifier.startsWith("placeholder_")) {
|
||||
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring("placeholder_".length()) + "%", TAB.getInstance().getPlaceholderManager());
|
||||
}
|
||||
if (player == null) return "<Player cannot be null>";
|
||||
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
|
||||
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
|
||||
return values.get(p).get(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"UnstableApiUsage", "deprecation"})
|
||||
public void unregisterExpansion() {
|
||||
if (ReflectionUtils.methodExists(PlaceholderExpansion.class, "unregister")) {
|
||||
// Added in 2.10.7 (Jul 28, 2020)
|
||||
unregister();
|
||||
} else {
|
||||
PlaceholderAPI.unregisterExpansion(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
|
||||
/**
|
||||
* Utility to execute /papi ecloud download <expansion> on expansions which are used but not installed
|
||||
* If an expansion was successfully downloaded (new expansion count is higher than previous in expansions folder),
|
||||
* /papi reload is executed.
|
||||
* Since this feature was added into the plugin amount of non-working placeholders caused by missing expansion went down rapidly.
|
||||
* I'm not sure why, must be a coinscidence.
|
||||
*/
|
||||
public class ExpansionDownloader {
|
||||
|
||||
//the plugin to run bukkit tasks as
|
||||
private JavaPlugin plugin;
|
||||
|
||||
/**
|
||||
* Constructs new instance of the class with provided plugin instance
|
||||
* @param plugin - plugin to run bukkit tasks as
|
||||
*/
|
||||
public ExpansionDownloader(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to download all included expansions using /papi ecloud download <expansion> command and if something was successful /papi reload
|
||||
* @param expansions - list of expansions to attempt to download
|
||||
*/
|
||||
public void download(Set<String> expansions) {
|
||||
//starting the task in the first tick just like PlaceholderAPI does to prevent downloading before PlaceholderAPI loads all expansions
|
||||
Bukkit.getScheduler().runTaskLater(plugin, new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//starting the task asynchronously to not freeze the server with Thread.sleep
|
||||
new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
//initial delay to give PlaceholderAPI & plugins with expansions time to load
|
||||
Thread.sleep(10000);
|
||||
TAB.getInstance().debug("Used expansions: " + expansions.toString());
|
||||
TAB.getInstance().debug("Registered expansions in PlaceholderAPI: " + PlaceholderAPI.getRegisteredIdentifiers());
|
||||
expansions.removeAll(PlaceholderAPI.getRegisteredIdentifiers());
|
||||
TAB.getInstance().debug("Expansions to install: " + expansions.toString());
|
||||
if (!expansions.isEmpty()) {
|
||||
File expansionsFolder = new File(Bukkit.getPluginManager().getPlugin("PlaceholderAPI").getDataFolder(), "expansions");
|
||||
int oldExpansionDownloadedCount = expansionsFolder.listFiles().length;
|
||||
for (String expansion : expansions) {
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a7d[TAB] Expansion \u00a7e" + expansion + "\u00a7d is used but not installed. Installing!");
|
||||
runSyncCommand("papi ecloud download " + expansion);
|
||||
Thread.sleep(5000);
|
||||
}
|
||||
if (expansionsFolder.listFiles().length > oldExpansionDownloadedCount) {
|
||||
Bukkit.getConsoleSender().sendMessage("\u00a7d[TAB] Reloading PlaceholderAPI for the changes to take effect.");
|
||||
runSyncCommand("papi reload");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException | ConcurrentModificationException e) {
|
||||
//fast shutdown probably
|
||||
} catch (NoClassDefFoundError e){
|
||||
//PAPI class not found, fast shutdown probably
|
||||
} catch (Throwable e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to download PlaceholderAPI expansions. PlaceholderAPI version: " + Bukkit.getPluginManager().getPlugin("PlaceholderAPI").getDescription().getVersion(), e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs given command in the main thread
|
||||
* @param command - command to perform
|
||||
*/
|
||||
private void runSyncCommand(String command) {
|
||||
//back to main thread as commands need to be ran in it
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||
} catch (Exception e) {
|
||||
//papi ecloud is disabled
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
import me.neznamy.tab.shared.features.types.UnLoadable;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Per-world-PlayerList feature handler
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
|
||||
|
||||
/** Config options */
|
||||
private final boolean allowBypass = 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");
|
||||
|
||||
/**
|
||||
* Constructs new instance and registers events
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance to register events
|
||||
*/
|
||||
public PerWorldPlayerList(JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
checkPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
for (Player pl : BukkitUtils.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
}
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(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(getFeatureName(), TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs visibility check on the player. Shows players this player should see and does not,
|
||||
* hides players the player should not see, shows the player to those who should see player and hides
|
||||
* from those, who should not see.
|
||||
*
|
||||
* @param p
|
||||
* Player to update
|
||||
*/
|
||||
private void checkPlayer(@NotNull Player p) {
|
||||
for (Player all : BukkitUtils.getOnlinePlayers()) {
|
||||
if (all == p) continue;
|
||||
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
|
||||
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
|
||||
if (!shouldSee(all, p) && all.canSee(p)) all.hidePlayer(p);
|
||||
if (shouldSee(all, p) && !all.canSee(p)) all.showPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if viewer should see target player, {@code false} if not.
|
||||
* @param viewer
|
||||
* Player viewing the TabList
|
||||
* @param target
|
||||
* Target displayed in the TabList
|
||||
* @return {@code true} if viewer should see target, {@code false} if not.
|
||||
*/
|
||||
private boolean shouldSee(@NotNull Player viewer, @NotNull Player target) {
|
||||
if (target == viewer) return true;
|
||||
if ((allowBypass && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || ignoredWorlds.contains(viewer.getWorld().getName())) return true;
|
||||
String viewerWorldGroup = viewer.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
|
||||
String targetWorldGroup = target.getWorld().getName() + "-default";
|
||||
for (Entry<String, List<String>> group : sharedWorlds.entrySet()) {
|
||||
if (group.getValue() != null) {
|
||||
if (group.getValue().contains(viewer.getWorld().getName())) viewerWorldGroup = group.getKey();
|
||||
if (group.getValue().contains(target.getWorld().getName())) targetWorldGroup = group.getKey();
|
||||
}
|
||||
}
|
||||
return viewerWorldGroup.equals(targetWorldGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getFeatureName() {
|
||||
return "Per world PlayerList";
|
||||
}
|
||||
}
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
|
||||
/**
|
||||
* Per-world-playerlist feature handler
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PerWorldPlayerlist implements Loadable, Listener {
|
||||
|
||||
//plugin instance
|
||||
private JavaPlugin plugin;
|
||||
|
||||
//config options
|
||||
private boolean allowBypass;
|
||||
private List<String> ignoredWorlds;
|
||||
private Map<String, List<String>> sharedWorlds;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
* @param plugin - plugin instance
|
||||
* @param tab - tab instance
|
||||
*/
|
||||
public PerWorldPlayerlist(JavaPlugin plugin, TAB tab) {
|
||||
this.plugin = plugin;
|
||||
allowBypass = tab.getConfiguration().config.getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
ignoredWorlds = tab.getConfiguration().config.getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
|
||||
sharedWorlds = tab.getConfiguration().config.getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
for (String group : sharedWorlds.keySet()) {
|
||||
if (sharedWorlds.get(group).size() == 1) {
|
||||
tab.getErrorManager().startupWarn("World group \"" + group + "\" in per-world-playerlist only contain a single world (\"" + sharedWorlds.get(group).get(0) +
|
||||
"\"), which has no effect and only makes config less readable. Delete the group entirely for a cleaner config.");
|
||||
}
|
||||
}
|
||||
tab.debug(String.format("Loaded PerWorldPlayerlist feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
Bukkit.getScheduler().runTask(plugin, () -> Bukkit.getOnlinePlayers().forEach(p -> checkPlayer(p)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload(){
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
}
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(TabFeature.PER_WORLD_PLAYERLIST, UsageType.PLAYER_JOIN_EVENT, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(TabFeature.PER_WORLD_PLAYERLIST, UsageType.WORLD_SWITCH_EVENT, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
private void checkPlayer(Player p) {
|
||||
for (Player all : Bukkit.getOnlinePlayers()){
|
||||
if (all == p) continue;
|
||||
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
|
||||
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
|
||||
if (!shouldSee(all, p) && all.canSee(p)) all.hidePlayer(p);
|
||||
if (shouldSee(all, p) && !all.canSee(p)) all.showPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSee(Player viewer, Player target) {
|
||||
if (target == viewer) return true;
|
||||
if ((allowBypass && viewer.hasPermission("tab.bypass")) || ignoredWorlds.contains(viewer.getWorld().getName())) return true;
|
||||
String viewerWorldGroup = viewer.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
|
||||
String targetWorldGroup = target.getWorld().getName() + "-default";
|
||||
for (String group : sharedWorlds.keySet()) {
|
||||
if (sharedWorlds.get(group) != null) {
|
||||
if (sharedWorlds.get(group).contains(viewer.getWorld().getName())) viewerWorldGroup = group;
|
||||
if (sharedWorlds.get(group).contains(target.getWorld().getName())) targetWorldGroup = group;
|
||||
}
|
||||
}
|
||||
return viewerWorldGroup.equals(targetWorldGroup);
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.features.types.event.QuitEventListener;
|
||||
import me.neznamy.tab.shared.features.types.packet.RawPacketListener;
|
||||
|
||||
/**
|
||||
* A feature to disable minecraft 1.9+ feature making tamed animals with custom names copy nametag properties of their owner
|
||||
* This is achieved by listening to entity spawn (<1.15) / entity metadata packets and removing owner field from the datawatcher list
|
||||
* Since 1.16 this results in client sending entity use packet twice, so we must cancel the 2nd one to prevent double toggle
|
||||
*/
|
||||
public class PetFix implements RawPacketListener, QuitEventListener {
|
||||
|
||||
//datawatcher position of pet owner field
|
||||
private int PET_OWNER_POSITION;
|
||||
|
||||
//logger of last interacts to prevent feature not working on 1.16
|
||||
private Map<String, Long> lastInteractFix = new HashMap<String, Long>();
|
||||
|
||||
//nms storage
|
||||
private NMSStorage nms;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
* @param nms
|
||||
*/
|
||||
public PetFix(NMSStorage nms) {
|
||||
this.nms = nms;
|
||||
PET_OWNER_POSITION = getPetOwnerPosition();
|
||||
TAB.getInstance().debug("Loaded PetFix feature");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns position of pet owner field based on server version
|
||||
* @return position of pet owner field based on server version
|
||||
*/
|
||||
private int getPetOwnerPosition() {
|
||||
if (nms.minorVersion >= 15) {
|
||||
//1.15.x, 1.16.x
|
||||
return 17;
|
||||
} else if (nms.minorVersion >= 14) {
|
||||
//1.14.x
|
||||
return 16;
|
||||
} else if (nms.minorVersion >= 10) {
|
||||
//1.10.x - 1.13.x
|
||||
return 14;
|
||||
} else {
|
||||
//1.9.x
|
||||
return 13;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a packet if previous one arrived with no delay to prevent double toggle on 1.16
|
||||
*/
|
||||
@Override
|
||||
public Object onPacketReceive(TabPlayer sender, Object packet) throws Throwable {
|
||||
if (nms.PacketPlayInUseEntity.isInstance(packet)) {
|
||||
if (lastInteractFix.containsKey(sender.getName()) && (System.currentTimeMillis() - lastInteractFix.get(sender.getName()) < 5)) {
|
||||
//last interact packet was sent right now, cancelling to prevent double-toggle due to this feature enabled
|
||||
return null;
|
||||
}
|
||||
if (nms.PacketPlayInUseEntity_ACTION.get(packet).toString().equals("INTERACT")) {
|
||||
//this is the first packet, saving player so the next packet can be cancelled
|
||||
lastInteractFix.put(sender.getName(), System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes pet owner field from datawatcher
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws Throwable {
|
||||
if (nms.PacketPlayOutEntityMetadata.isInstance(packet)) {
|
||||
List<Object> items = (List<Object>) nms.PacketPlayOutEntityMetadata_LIST.get(packet);
|
||||
if (items == null) return;
|
||||
List<Object> newList = new ArrayList<Object>();
|
||||
for (Object item : items) {
|
||||
if (nms.DataWatcherObject_SLOT.getInt(nms.DataWatcherItem_TYPE.get(item)) == PET_OWNER_POSITION) {
|
||||
Object value = nms.DataWatcherItem_VALUE.get(item);
|
||||
if (value.getClass().getSimpleName().equals("Optional")) continue;
|
||||
}
|
||||
newList.add(item);
|
||||
}
|
||||
nms.PacketPlayOutEntityMetadata_LIST.set(packet, newList);
|
||||
}
|
||||
//<1.15
|
||||
if (nms.PacketPlayOutSpawnEntityLiving.isInstance(packet) && nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER != null) {
|
||||
DataWatcher watcher = DataWatcher.fromNMS(nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER.get(packet));
|
||||
DataWatcherItem petOwner = watcher.getItem(PET_OWNER_POSITION);
|
||||
if (petOwner != null && petOwner.value.getClass().getSimpleName().equals("Optional")) {
|
||||
watcher.removeValue(PET_OWNER_POSITION);
|
||||
nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER.set(packet, watcher.toNMS());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabFeature getFeatureType() {
|
||||
return TabFeature.PET_NAME_FIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removing player data to not have a memory leak
|
||||
*/
|
||||
@Override
|
||||
public void onQuit(TabPlayer disconnectedPlayer) {
|
||||
lastInteractFix.remove(disconnectedPlayer.getName());
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.placeholders.Placeholder;
|
||||
|
||||
/**
|
||||
* TAB's expansion for PlaceholderAPI
|
||||
*/
|
||||
public class TabExpansion extends PlaceholderExpansion {
|
||||
|
||||
//plugin to take plugin.yml data from
|
||||
private JavaPlugin plugin;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs new instance of the class and registers it
|
||||
* @param plugin - plugin to take plugin.yml data from
|
||||
*/
|
||||
public TabExpansion(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean persist(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRegister(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthor(){
|
||||
return plugin.getDescription().getAuthors().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier(){
|
||||
return "tab";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion(){
|
||||
return plugin.getDescription().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onPlaceholderRequest(Player player, String identifier){
|
||||
if (player == null) return "";
|
||||
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
|
||||
if (identifier.equals("scoreboard_visible")) {
|
||||
return translate(p.isScoreboardVisible());
|
||||
}
|
||||
if (identifier.equals("bossbar_visible")) {
|
||||
return translate(p.hasBossbarVisible());
|
||||
}
|
||||
if (identifier.equals("ntpreview")) {
|
||||
return translate(p.isPreviewingNametag());
|
||||
}
|
||||
if (identifier.startsWith("replace_")) {
|
||||
return findReplacement("%" + identifier.substring(8) + "%", player);
|
||||
}
|
||||
if (identifier.startsWith("placeholder_")) {
|
||||
//using Property function for fast & easy handling of nested placeholders and different placeholder types
|
||||
return new Property(p, "%" + identifier.substring(12) + "%").get();
|
||||
}
|
||||
return getProperty(identifier, p);
|
||||
}
|
||||
|
||||
private String translate(boolean b) {
|
||||
return b ? "Enabled" : "Disabled";
|
||||
}
|
||||
|
||||
/**
|
||||
* Find replacement for specified placeholder
|
||||
* @param placeholder - placeholder to find replacement for
|
||||
* @param player - player to set placeholder for
|
||||
* @return replacement
|
||||
*/
|
||||
private String findReplacement(String placeholder, Player player) {
|
||||
if (!TAB.getInstance().isPremium()) return "Placeholder output replacements require premium version to work";
|
||||
String output = ((BukkitPlatform) TAB.getInstance().getPlatform()).setPlaceholders(player, placeholder);
|
||||
Map<Object, String> replacements = TAB.getInstance().getConfiguration().premiumconfig.getConfigurationSection("placeholder-output-replacements." + placeholder);
|
||||
return Placeholder.findReplacement(replacements, output).replace("%value%", output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value of defined property if found
|
||||
* @param name - property name
|
||||
* @param player - player to get property of
|
||||
* @return value of specified property
|
||||
*/
|
||||
private String getProperty(String name, TabPlayer player) {
|
||||
String propName = name.replace("_raw", "");
|
||||
Property prop = player.getProperty(propName);
|
||||
if (prop != null) {
|
||||
if (name.endsWith("_raw")) {
|
||||
return prop.getCurrentRawValue();
|
||||
}
|
||||
return prop.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.api.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.features.types.WorldSwitchListener;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An additional class with additional code for <1.9 servers due to an entity being required
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
|
||||
|
||||
/** Distance of the wither in blocks */
|
||||
private static final int WITHER_DISTANCE = 60;
|
||||
|
||||
/**
|
||||
* Constructs new instance and registers events
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance to register events
|
||||
*/
|
||||
public WitherBossBar(JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
|
||||
//when in a small window, it's about 100ms
|
||||
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
|
||||
getFeatureName(), TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
super.load();
|
||||
teleport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Wither location for all online players
|
||||
*/
|
||||
private void teleport() {
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
|
||||
for (BossBar line : getRegisteredBossBars().values()) {
|
||||
if (!line.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).getEntityView().teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
super.unload();
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning wither as respawn screen destroys all entities in client.
|
||||
*
|
||||
* @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(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
|
||||
() -> detectBossBarsAndSend(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWorldChange(@NotNull TabPlayer p, @NotNull String from, @NotNull String to) {
|
||||
for (BossBar line : lineValues) {
|
||||
line.removePlayer(p);
|
||||
}
|
||||
detectBossBarsAndSend(p);
|
||||
}
|
||||
}
|
||||
-387
@@ -1,387 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Pose;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import me.neznamy.tab.api.ArmorStand;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitPacketBuilder;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.packets.IChatBaseComponent;
|
||||
|
||||
/**
|
||||
* A class representing an armor stand attached to a player (if the feature is enabled)
|
||||
*/
|
||||
public class BukkitArmorStand implements ArmorStand {
|
||||
|
||||
//owner of the armor stand
|
||||
private TabPlayer owner;
|
||||
|
||||
//instance of Bukkit player
|
||||
private Player player;
|
||||
|
||||
//offset in blocks, 0 for original height
|
||||
private double yOffset;
|
||||
|
||||
//entity ID of this armor stand
|
||||
private int entityId;
|
||||
|
||||
//unique ID of this armor stand
|
||||
private UUID uuid = UUID.randomUUID();
|
||||
|
||||
//sneaking flag of armor stands
|
||||
private boolean sneaking;
|
||||
|
||||
//armor stand visibility
|
||||
private boolean visible;
|
||||
|
||||
//list of players in entity tracking range (48 blocks)
|
||||
private Set<TabPlayer> nearbyPlayers = Collections.synchronizedSet(new HashSet<TabPlayer>());
|
||||
|
||||
//property dedicated to this armor stand
|
||||
private Property property;
|
||||
|
||||
//if offset is static or dynamic based on other armor stands
|
||||
private boolean staticOffset;
|
||||
|
||||
//if marker tag should be used for 1.8.x clients
|
||||
private boolean markerFor18x;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param owner - armor stand owner
|
||||
* @param property - property for armor stand's name
|
||||
* @param yOffset - offset in blocks
|
||||
* @param staticOffset - if offset is static or not
|
||||
*/
|
||||
public BukkitArmorStand(int entityId, TabPlayer owner, Property property, double yOffset, boolean staticOffset) {
|
||||
this.entityId = entityId;
|
||||
this.owner = owner;
|
||||
this.staticOffset = staticOffset;
|
||||
player = (Player) owner.getPlayer();
|
||||
this.yOffset = yOffset;
|
||||
this.property = property;
|
||||
markerFor18x = ((NameTagX)TAB.getInstance().getFeatureManager().getFeature("nametagx")).markerFor18x;
|
||||
visible = getVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
visible = getVisibility();
|
||||
updateMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStaticOffset() {
|
||||
return staticOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getOffset() {
|
||||
return yOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOffset(double offset) {
|
||||
if (yOffset == offset) return;
|
||||
yOffset = offset;
|
||||
for (TabPlayer all : getNearbyPlayers()) {
|
||||
all.sendPacket(getTeleportPacket(all), TabFeature.NAMETAGX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of packets to send to make armor stand spawn with metadata
|
||||
* @param viewer - viewer to apply relational placeholders for
|
||||
* @param addToRegistered - if player should be added to registered or not
|
||||
* @return List of packets that spawn the armor stand
|
||||
*/
|
||||
public Object[] getSpawnPackets(TabPlayer viewer) {
|
||||
visible = getVisibility();
|
||||
nearbyPlayers.add(viewer);
|
||||
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
|
||||
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 15) {
|
||||
return new Object[] {
|
||||
getSpawnPacket(getArmorStandLocationFor(viewer), null),
|
||||
getMetadataPacket(dataWatcher)
|
||||
};
|
||||
} else {
|
||||
return new Object[] {
|
||||
getSpawnPacket(getArmorStandLocationFor(viewer), dataWatcher)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawn(TabPlayer viewer) {
|
||||
for (Object packet : getSpawnPackets(viewer)) {
|
||||
viewer.sendPacket(packet, TabFeature.NAMETAGX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns location where armor stand should be for specified viewer
|
||||
* @param viewer - player to get location for
|
||||
* @return location of armor stand
|
||||
*/
|
||||
private Location getArmorStandLocationFor(TabPlayer viewer) {
|
||||
return viewer.getVersion().getMinorVersion() == 8 && !markerFor18x ? getLocation().clone().add(0,-2,0) : getLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(TabPlayer viewer) {
|
||||
nearbyPlayers.remove(viewer);
|
||||
viewer.sendPacket(getDestroyPacket(), TabFeature.NAMETAGX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleport() {
|
||||
for (TabPlayer all : getNearbyPlayers()) {
|
||||
all.sendPacket(getTeleportPacket(all), TabFeature.NAMETAGX);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleport(TabPlayer viewer) {
|
||||
viewer.sendPacket(getTeleportPacket(viewer), TabFeature.NAMETAGX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sneak(boolean sneaking) {
|
||||
if (this.sneaking == sneaking) return; //idk
|
||||
this.sneaking = sneaking;
|
||||
for (TabPlayer viewer : getNearbyPlayers()) {
|
||||
if (viewer.getVersion().getMinorVersion() == 14 && !TAB.getInstance().getConfiguration().armorStandsAlwaysVisible) {
|
||||
//1.14.x client sided bug, despawning completely
|
||||
if (sneaking) {
|
||||
viewer.sendPacket(getDestroyPacket(), TabFeature.NAMETAGX);
|
||||
} else {
|
||||
spawn(viewer);
|
||||
}
|
||||
} else {
|
||||
//respawning so there's no animation and it's instant
|
||||
viewer.sendPacket(getDestroyPacket(), TabFeature.NAMETAGX);
|
||||
Runnable spawn = () -> spawn(viewer);
|
||||
if (viewer.getVersion().getMinorVersion() == 8) {
|
||||
//1.8.0 client sided bug
|
||||
TAB.getInstance().getCPUManager().runTaskLater(50, "compensating for 1.8.0 bugs", TabFeature.NAMETAGX, UsageType.v1_8_0_BUG_COMPENSATION, spawn);
|
||||
} else {
|
||||
spawn.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
for (TabPlayer all : TAB.getInstance().getPlayers()) all.sendPacket(getDestroyPacket(), TabFeature.NAMETAGX);
|
||||
nearbyPlayers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVisibility(boolean force) {
|
||||
boolean visible = getVisibility();
|
||||
if (this.visible != visible || force) {
|
||||
this.visible = visible;
|
||||
updateMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns teleport packet for specified viewer
|
||||
* @param viewer - player to get location for
|
||||
* @return teleport packet
|
||||
*/
|
||||
private Object getTeleportPacket(TabPlayer viewer) {
|
||||
try {
|
||||
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityTeleportPacket(entityId, getArmorStandLocationFor(viewer));
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to create PacketPlayOutEntityTeleport", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns destroy packet
|
||||
* @return destroy packet
|
||||
*/
|
||||
private Object getDestroyPacket() {
|
||||
try {
|
||||
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityDestroyPacket(entityId);
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to create PacketPlayOutEntityDestroy", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns metadata packet with specified datawatcher
|
||||
* @param dataWatcher - datawatcher
|
||||
* @return metadata packet
|
||||
*/
|
||||
private Object getMetadataPacket(DataWatcher dataWatcher) {
|
||||
try {
|
||||
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityMetadataPacket(entityId, dataWatcher);
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to create PacketPlayOutEntityMetadata", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns spawn packet with specified parameters
|
||||
* @param loc - location to spawn at
|
||||
* @param dataWatcher - datawatcher
|
||||
* @return spawn packet
|
||||
*/
|
||||
private Object getSpawnPacket(Location loc, DataWatcher dataWatcher) {
|
||||
try {
|
||||
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntitySpawnPacket(entityId, uuid, EntityType.ARMOR_STAND, loc, dataWatcher);
|
||||
} catch (Exception e) {
|
||||
return TAB.getInstance().getErrorManager().printError(null, "Failed to create PacketPlayOutSpawnEntityLiving", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates armor stand's metadata
|
||||
*/
|
||||
private void updateMetadata() {
|
||||
for (TabPlayer viewer : getNearbyPlayers()) {
|
||||
viewer.sendPacket(getMetadataPacket(createDataWatcher(property.getFormat(viewer), viewer)), TabFeature.NAMETAGX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns general visibility rule for everyone with limited info
|
||||
* @return true if should be visible, false if not
|
||||
*/
|
||||
private boolean getVisibility() {
|
||||
if (((BukkitTabPlayer)owner).isDisguised() || (((NameTagX)TAB.getInstance().getFeatureManager().getFeature("nametagx")).playersOnBoats.contains(owner))) return false;
|
||||
if (TAB.getInstance().getConfiguration().armorStandsAlwaysVisible) return true;
|
||||
return !player.hasPotionEffect(PotionEffectType.INVISIBILITY) && player.getGameMode() != GameMode.SPECTATOR && !owner.hasHiddenNametag() && property.get().length() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns general location where armor stand should be at time of calling
|
||||
* @return Location where armor stand should be for everyone
|
||||
*/
|
||||
private Location getLocation() {
|
||||
double x = player.getLocation().getX();
|
||||
double y = getY() + yOffset + 2;
|
||||
double z = player.getLocation().getZ();
|
||||
if (player.isSleeping()) {
|
||||
y -= 1.76;
|
||||
} else {
|
||||
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 9) {
|
||||
y -= (sneaking ? 0.45 : 0.18);
|
||||
} else {
|
||||
y -= (sneaking ? 0.30 : 0.18);
|
||||
}
|
||||
}
|
||||
return new Location(null,x,y,z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Y where player is based on player's vehicle due to bukkit API bug
|
||||
* @return correct player's Y
|
||||
*/
|
||||
private double getY() {
|
||||
//1.14+ server sided bug
|
||||
Entity vehicle = player.getVehicle();
|
||||
if (vehicle != null) {
|
||||
if (vehicle.getType().toString().contains("HORSE")) { //covering all 3 horse types
|
||||
return vehicle.getLocation().getY() + 0.85;
|
||||
}
|
||||
if (vehicle.getType() == EntityType.DONKEY) {
|
||||
return vehicle.getLocation().getY() + 0.525;
|
||||
}
|
||||
if (vehicle.getType() == EntityType.PIG) {
|
||||
return vehicle.getLocation().getY() + 0.325;
|
||||
}
|
||||
if (vehicle.getType().toString().equals("STRIDER")) { //added in 1.16
|
||||
return vehicle.getLocation().getY() + 1.15;
|
||||
}
|
||||
}
|
||||
//1.13+ swimming or 1.9+ flying with elytra
|
||||
if (isSwimming() || (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 9 && player.isGliding())) {
|
||||
return player.getLocation().getY()-1.22;
|
||||
}
|
||||
return player.getLocation().getY();
|
||||
}
|
||||
|
||||
private boolean isSwimming() {
|
||||
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
|
||||
return ProtocolVersion.SERVER_VERSION.getMinorVersion() == 13 && player.isSwimming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFromRegistered(TabPlayer viewer) {
|
||||
nearbyPlayers.remove(viewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates data watcher with specified display name for viewer
|
||||
* @param displayName - armor stand name
|
||||
* @param viewer - player to apply checks against
|
||||
* @return datawatcher for viewer
|
||||
*/
|
||||
private DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
|
||||
DataWatcher datawatcher = new DataWatcher();
|
||||
|
||||
byte flag = 32; //invisible
|
||||
if (sneaking) flag += (byte)2;
|
||||
datawatcher.helper().setEntityFlags(flag);
|
||||
datawatcher.helper().setCustomName(displayName, viewer.getVersion());
|
||||
|
||||
boolean visible;
|
||||
if (isNameVisiblyEmpty(displayName) || !((Player) viewer.getPlayer()).canSee(player) || owner.hasHiddenNametag(viewer.getUniqueId())) {
|
||||
visible = false;
|
||||
} else {
|
||||
visible = this.visible;
|
||||
}
|
||||
datawatcher.helper().setCustomNameVisible(visible);
|
||||
|
||||
if (viewer.getVersion().getMinorVersion() > 8 || markerFor18x) datawatcher.helper().setArmorStandFlags((byte)16);
|
||||
return datawatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<TabPlayer> getNearbyPlayers(){
|
||||
synchronized (nearbyPlayers) {
|
||||
return new HashSet<TabPlayer>(nearbyPlayers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if display name is in fact empty, for example only containing color codes
|
||||
* @param displayName - string to check
|
||||
* @return true if it's empty, false if not
|
||||
*/
|
||||
private boolean isNameVisiblyEmpty(String displayName) {
|
||||
return IChatBaseComponent.fromColoredText(displayName).toRawText().replace(" ", "").length() == 0;
|
||||
}
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
|
||||
/**
|
||||
* The event listener part for securing proper functionality of armor stands
|
||||
*/
|
||||
public class EventListener implements Listener {
|
||||
|
||||
//the nametag feature handler
|
||||
private NameTagX feature;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param feature - nametagx feature handler
|
||||
*/
|
||||
public EventListener(NameTagX feature) {
|
||||
this.feature = feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sneak event listener to despawn & spawn armor stands to skip animation
|
||||
* @param e - sneak event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onSneak(PlayerToggleSneakEvent e) {
|
||||
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (p == null || !p.isLoaded() || feature.isDisabledWorld(p.getWorldName()) || feature.isDisabledWorld(feature.disabledUnlimitedWorlds, p.getWorldName())) return;
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask("processing PlayerToggleSneakEvent", TabFeature.NAMETAGX, UsageType.PLAYER_TOGGLE_SNEAK_EVENT, () -> {
|
||||
if (p.getArmorStandManager() != null) p.getArmorStandManager().sneak(e.isSneaking());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning armor stands as respawn screen destroys all entities in client
|
||||
* @param e - respawn event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask("processing PlayerRespawnEvent", TabFeature.NAMETAGX, UsageType.PLAYER_RESPAWN_EVENT, () -> {
|
||||
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (feature.isDisabledWorld(respawned.getWorldName()) || feature.isDisabledWorld(feature.disabledUnlimitedWorlds, respawned.getWorldName())) return;
|
||||
respawned.getArmorStandManager().teleport();
|
||||
});
|
||||
}
|
||||
}
|
||||
-405
@@ -1,405 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.ArmorStand;
|
||||
import me.neznamy.tab.api.ArmorStandManager;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.features.NameTag;
|
||||
|
||||
/**
|
||||
* The core class for unlimited nametag mode
|
||||
*/
|
||||
public class NameTagX extends NameTag {
|
||||
|
||||
//config options
|
||||
public boolean markerFor18x;
|
||||
public boolean disableOnBoats;
|
||||
private double spaceBetweenLines;
|
||||
|
||||
//list of worlds with unlimited nametag mode disabled
|
||||
protected List<String> disabledUnlimitedWorlds;
|
||||
|
||||
//list of defined dynamic lines
|
||||
public List<String> dynamicLines = Arrays.asList("belowname", "nametag", "abovename");
|
||||
|
||||
//map of defined static lines
|
||||
public Map<String, Object> staticLines = new ConcurrentHashMap<String, Object>();
|
||||
|
||||
//entity id counter to pick unique entity IDs
|
||||
private int idCounter = 2000000000;
|
||||
|
||||
//player data by entityId, used for better performance
|
||||
public Map<Integer, TabPlayer> entityIdMap = new ConcurrentHashMap<Integer, TabPlayer>();
|
||||
|
||||
//map of vehicles carrying players
|
||||
public Map<Integer, List<Entity>> vehicles = new ConcurrentHashMap<Integer, List<Entity>>();
|
||||
|
||||
//bukkit event listener
|
||||
private EventListener eventListener;
|
||||
|
||||
//list of players currently on boats
|
||||
public List<TabPlayer> playersOnBoats = new ArrayList<TabPlayer>();
|
||||
|
||||
//list of players currently in a vehicle
|
||||
private Map<TabPlayer, Entity> playersInVehicle = new ConcurrentHashMap<TabPlayer, Entity>();
|
||||
|
||||
private Map<TabPlayer, Location> playerLocations = new ConcurrentHashMap<TabPlayer, Location>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
* @param plugin - plugin instance
|
||||
* @param nms - nms storage
|
||||
* @param tab - tab instance
|
||||
*/
|
||||
public NameTagX(JavaPlugin plugin, NMSStorage nms, TAB tab) {
|
||||
super(tab);
|
||||
markerFor18x = tab.getConfiguration().config.getBoolean("unlimited-nametag-prefix-suffix-mode.use-marker-tag-for-1-8-x-clients", false);
|
||||
disableOnBoats = tab.getConfiguration().config.getBoolean("unlimited-nametag-prefix-suffix-mode.disable-on-boats", true);
|
||||
spaceBetweenLines = tab.getConfiguration().config.getDouble("unlimited-nametag-prefix-suffix-mode.space-between-lines", 0.22);
|
||||
disabledUnlimitedWorlds = tab.getConfiguration().config.getStringList("disable-features-in-worlds.unlimited-nametags", Arrays.asList("disabledworld"));
|
||||
if (tab.getConfiguration().premiumconfig != null) {
|
||||
List<String> realList = tab.getConfiguration().premiumconfig.getStringList("unlimited-nametag-mode-dynamic-lines", Arrays.asList("abovename", "nametag", "belowname", "another"));
|
||||
dynamicLines = new ArrayList<String>();
|
||||
dynamicLines.addAll(realList);
|
||||
Collections.reverse(dynamicLines);
|
||||
staticLines = tab.getConfiguration().premiumconfig.getConfigurationSection("unlimited-nametag-mode-static-lines");
|
||||
}
|
||||
refreshUsedPlaceholders();
|
||||
eventListener = new EventListener(this);
|
||||
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
|
||||
tab.getFeatureManager().registerFeature("nametagx-packet", new PacketListener(this, nms, tab));
|
||||
tab.debug(String.format("Loaded Unlimited nametag feature with parameters markerFor18x=%s, disableOnBoats=%s, spaceBetweenLines=%s, disabledUnlimitedWorlds=%s",
|
||||
markerFor18x, disableOnBoats, spaceBetweenLines, disabledUnlimitedWorlds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
super.load();
|
||||
for (TabPlayer all : tab.getPlayers()) {
|
||||
entityIdMap.put(((Player) all.getPlayer()).getEntityId(), all);
|
||||
loadArmorStands(all);
|
||||
if (isDisabledWorld(all.getWorldName()) || isDisabledWorld(disabledUnlimitedWorlds, all.getWorldName())) continue;
|
||||
loadPassengers(all);
|
||||
for (TabPlayer viewer : tab.getPlayers()) {
|
||||
spawnArmorStands(all, viewer, false);
|
||||
}
|
||||
}
|
||||
startVisibilityRefreshTask();
|
||||
startVehicleTickingTask();
|
||||
}
|
||||
|
||||
private void startVisibilityRefreshTask() {
|
||||
tab.getCPUManager().startRepeatingMeasuredTask(500, "refreshing nametag visibility", getFeatureType(), UsageType.REFRESHING_NAMETAG_VISIBILITY_AND_COLLISION, new Runnable() {
|
||||
public void run() {
|
||||
for (TabPlayer p : tab.getPlayers()) {
|
||||
if (!p.isLoaded() || isDisabledWorld(p.getWorldName()) || isDisabledWorld(disabledUnlimitedWorlds, p.getWorldName())) continue;
|
||||
p.getArmorStandManager().updateVisibility(false);
|
||||
if (!disableOnBoats) continue;
|
||||
boolean onBoat = ((Player)p.getPlayer()).getVehicle() != null && ((Player)p.getPlayer()).getVehicle().getType() == EntityType.BOAT;
|
||||
if (onBoat) {
|
||||
if (!playersOnBoats.contains(p)) {
|
||||
playersOnBoats.add(p);
|
||||
updateTeamData(p);
|
||||
}
|
||||
} else {
|
||||
if (playersOnBoats.contains(p)) {
|
||||
playersOnBoats.remove(p);
|
||||
updateTeamData(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startVehicleTickingTask() {
|
||||
tab.getCPUManager().startRepeatingMeasuredTask(100, "ticking vehicles", TabFeature.NAMETAGX, UsageType.TICKING_VEHICLES, () -> {
|
||||
|
||||
for (TabPlayer p : TAB.getInstance().getPlayers()) {
|
||||
if (!p.isLoaded()) continue;
|
||||
if (isDisabledWorld(p.getWorldName()) || isDisabledWorld(disabledUnlimitedWorlds, p.getWorldName())) {
|
||||
playersInVehicle.remove(p);
|
||||
playerLocations.remove(p);
|
||||
continue;
|
||||
}
|
||||
processVehicles(p);
|
||||
if (!playerLocations.containsKey(p) || !playerLocations.get(p).equals(((Player)p.getPlayer()).getLocation())) {
|
||||
playerLocations.put(p, ((Player)p.getPlayer()).getLocation());
|
||||
processPassengers((Entity) p.getPlayer());
|
||||
//also updating position if player is previewing since we're here as the code would be same if we want to avoid listening to move event
|
||||
if (p.isPreviewingNametag() && p.getArmorStandManager() != null) {
|
||||
p.getArmorStandManager().teleport(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for vehicle changes of player and sends packets if needed
|
||||
* @param p - player to check
|
||||
*/
|
||||
private void processVehicles(TabPlayer p) {
|
||||
Entity vehicle = ((Player)p.getPlayer()).getVehicle();
|
||||
if (playersInVehicle.containsKey(p) && vehicle == null) {
|
||||
//vehicle exit
|
||||
vehicles.remove(playersInVehicle.get(p).getEntityId());
|
||||
p.getArmorStandManager().teleport();
|
||||
playersInVehicle.remove(p);
|
||||
}
|
||||
if (!playersInVehicle.containsKey(p) && vehicle != null) {
|
||||
//vehicle enter
|
||||
vehicles.put(vehicle.getEntityId(), getPassengers(vehicle));
|
||||
p.getArmorStandManager().teleport();
|
||||
playersInVehicle.put(p, vehicle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleports armor stands of all passengers on specified vehicle
|
||||
* @param vehicle - entity to check passengers of
|
||||
*/
|
||||
private void processPassengers(Entity vehicle) {
|
||||
for (Entity passenger : getPassengers(vehicle)) {
|
||||
if (passenger instanceof Player) {
|
||||
TabPlayer pl = TAB.getInstance().getPlayer(passenger.getUniqueId());
|
||||
pl.getArmorStandManager().teleport();
|
||||
} else {
|
||||
processPassengers(passenger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
super.unload();
|
||||
HandlerList.unregisterAll(eventListener);
|
||||
for (TabPlayer p : tab.getPlayers()) {
|
||||
if (p.getArmorStandManager() != null) p.getArmorStandManager().destroy();
|
||||
}
|
||||
entityIdMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onJoin(TabPlayer connectedPlayer) {
|
||||
super.onJoin(connectedPlayer);
|
||||
entityIdMap.put(((Player) connectedPlayer.getPlayer()).getEntityId(), connectedPlayer);
|
||||
loadArmorStands(connectedPlayer);
|
||||
if (isDisabledWorld(connectedPlayer.getWorldName()) || isDisabledWorld(disabledUnlimitedWorlds, connectedPlayer.getWorldName())) return;
|
||||
loadPassengers(connectedPlayer);
|
||||
for (TabPlayer viewer : tab.getPlayers()) {
|
||||
spawnArmorStands(connectedPlayer, viewer, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns flat distance between two players ignoring Y value
|
||||
* @param player1 - first player
|
||||
* @param player2 - second player
|
||||
* @return flat distance in blocks
|
||||
*/
|
||||
private double getDistance(TabPlayer player1, TabPlayer player2) {
|
||||
Location loc1 = ((Player) player1.getPlayer()).getLocation();
|
||||
Location loc2 = ((Player) player2.getPlayer()).getLocation();
|
||||
return Math.sqrt(Math.pow(loc1.getX()-loc2.getX(), 2) + Math.pow(loc1.getZ()-loc2.getZ(), 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all passengers riding this player and adds them to vehicle list
|
||||
* @param p - player to load passengers of
|
||||
*/
|
||||
private void loadPassengers(TabPlayer p) {
|
||||
if (((Entity) p.getPlayer()).getVehicle() == null) return;
|
||||
Entity vehicle = ((Entity) p.getPlayer()).getVehicle();
|
||||
vehicles.put(vehicle.getEntityId(), getPassengers(vehicle));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQuit(TabPlayer disconnectedPlayer) {
|
||||
super.onQuit(disconnectedPlayer);
|
||||
for (TabPlayer all : tab.getPlayers()) {
|
||||
if (all.getArmorStandManager() != null) all.getArmorStandManager().unregisterPlayer(disconnectedPlayer);
|
||||
}
|
||||
entityIdMap.remove(((Player) disconnectedPlayer.getPlayer()).getEntityId());
|
||||
playersInVehicle.remove(disconnectedPlayer);
|
||||
playerLocations.remove(disconnectedPlayer);
|
||||
tab.getCPUManager().runTaskLater(100, "processing onQuit", getFeatureType(), UsageType.PLAYER_QUIT_EVENT, () -> disconnectedPlayer.getArmorStandManager().destroy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWorldChange(TabPlayer p, String from, String to) {
|
||||
super.onWorldChange(p, from, to);
|
||||
Set<TabPlayer> nearby = p.getArmorStandManager().getNearbyPlayers();
|
||||
p.getArmorStandManager().destroy();
|
||||
loadArmorStands(p);
|
||||
loadPassengers(p);
|
||||
for (TabPlayer viewer : tab.getPlayers()) {
|
||||
viewer.getArmorStandManager().destroy(p);
|
||||
if (nearby.contains(viewer) && to.equals(viewer.getWorldName())) spawnArmorStands(p, viewer, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns armor stands of specified owner to viewer and, mutually if set
|
||||
* @param owner - armor stand owner
|
||||
* @param viewer - player to spawn armor stands for
|
||||
* @param sendMutually - if packets should be sent both ways
|
||||
*/
|
||||
private void spawnArmorStands(TabPlayer owner, TabPlayer viewer, boolean sendMutually) {
|
||||
if (owner == viewer) return; //not displaying own armorstands
|
||||
if (((Player) viewer.getPlayer()).getWorld() != ((Player) owner.getPlayer()).getWorld()) return; //different world
|
||||
if (isDisabledWorld(owner.getWorldName()) || isDisabledWorld(disabledUnlimitedWorlds, owner.getWorldName())) return;
|
||||
if (getDistance(viewer, owner) <= 48) {
|
||||
if (((Player)viewer.getPlayer()).canSee((Player)owner.getPlayer()) && !owner.isVanished()) owner.getArmorStandManager().spawn(viewer);
|
||||
if (sendMutually && viewer.getArmorStandManager() != null && ((Player)owner.getPlayer()).canSee((Player)viewer.getPlayer())
|
||||
&& !viewer.isVanished()) viewer.getArmorStandManager().spawn(owner);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts and loads armor stands from config
|
||||
* @param pl - player to load
|
||||
*/
|
||||
public void loadArmorStands(TabPlayer pl) {
|
||||
pl.setArmorStandManager(new ArmorStandManager());
|
||||
pl.setProperty("nametag", pl.getProperty("tagprefix").getCurrentRawValue() + pl.getProperty("customtagname").getCurrentRawValue() + pl.getProperty("tagsuffix").getCurrentRawValue());
|
||||
double height = -spaceBetweenLines;
|
||||
for (String line : dynamicLines) {
|
||||
Property p = pl.getProperty(line);
|
||||
if (p.getCurrentRawValue().length() == 0) continue;
|
||||
pl.getArmorStandManager().addArmorStand(line, new BukkitArmorStand(idCounter++, pl, p, height+=spaceBetweenLines, false));
|
||||
}
|
||||
for (Entry<String, Object> line : staticLines.entrySet()) {
|
||||
Property p = pl.getProperty(line.getKey());
|
||||
if (p.getCurrentRawValue().length() == 0) continue;
|
||||
pl.getArmorStandManager().addArmorStand(line.getKey(), new BukkitArmorStand(idCounter++, pl, p, Double.parseDouble(line.getValue().toString()), true));
|
||||
}
|
||||
fixArmorStandHeights(pl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes heights of all armor stands of specified player due to dynamic lines
|
||||
* @param p - player to fix armor stands heights for
|
||||
*/
|
||||
public void fixArmorStandHeights(TabPlayer p) {
|
||||
p.getArmorStandManager().refresh();
|
||||
double currentY = -spaceBetweenLines;
|
||||
for (ArmorStand as : p.getArmorStandManager().getArmorStands()) {
|
||||
if (as.hasStaticOffset()) continue;
|
||||
if (as.getProperty().get().length() != 0) {
|
||||
currentY += spaceBetweenLines;
|
||||
as.setOffset(currentY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(TabPlayer refreshed, boolean force) {
|
||||
super.refresh(refreshed, force);
|
||||
if (isDisabledWorld(refreshed.getWorldName()) || isDisabledWorld(disabledUnlimitedWorlds, refreshed.getWorldName())) return;
|
||||
if (force) {
|
||||
refreshed.getArmorStandManager().destroy();
|
||||
loadArmorStands(refreshed);
|
||||
loadPassengers(refreshed);
|
||||
for (TabPlayer viewer : tab.getPlayers()) {
|
||||
if (viewer == refreshed) continue;
|
||||
if (viewer.getWorldName().equals(refreshed.getWorldName())) {
|
||||
refreshed.getArmorStandManager().spawn(viewer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boolean fix = false;
|
||||
for (ArmorStand as : refreshed.getArmorStandManager().getArmorStands()) {
|
||||
if (as.getProperty().update()) {
|
||||
as.refresh();
|
||||
fix = true;
|
||||
}
|
||||
}
|
||||
if (fix) fixArmorStandHeights(refreshed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates raw values of properties for specified player
|
||||
* @param p - player to update
|
||||
*/
|
||||
public void updateProperties(TabPlayer p) {
|
||||
super.updateProperties(p);
|
||||
p.loadPropertyFromConfig("customtagname", p.getName());
|
||||
p.setProperty("nametag", p.getProperty("tagprefix").getCurrentRawValue() + p.getProperty("customtagname").getCurrentRawValue() + p.getProperty("tagsuffix").getCurrentRawValue());
|
||||
for (String property : dynamicLines) {
|
||||
if (!property.equals("nametag")) p.loadPropertyFromConfig(property);
|
||||
}
|
||||
for (String property : staticLines.keySet()) {
|
||||
if (!property.equals("nametag")) p.loadPropertyFromConfig(property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of all passengers on specified vehicle
|
||||
* @param vehicle - vehicle to check passengers of
|
||||
* @return list of passengers
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public List<Entity> getPassengers(Entity vehicle){
|
||||
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 11) {
|
||||
return vehicle.getPassengers();
|
||||
} else {
|
||||
if (vehicle.getPassenger() != null) {
|
||||
return Arrays.asList(vehicle.getPassenger());
|
||||
} else {
|
||||
return new ArrayList<Entity>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshUsedPlaceholders() {
|
||||
usedPlaceholders = new HashSet<>(tab.getConfiguration().config.getUsedPlaceholderIdentifiersRecursive("tagprefix", "customtagname", "tagsuffix"));
|
||||
for (String line : dynamicLines) {
|
||||
usedPlaceholders.addAll(tab.getConfiguration().config.getUsedPlaceholderIdentifiersRecursive(line));
|
||||
}
|
||||
for (String line : staticLines.keySet()) {
|
||||
usedPlaceholders.addAll(tab.getConfiguration().config.getUsedPlaceholderIdentifiersRecursive(line));
|
||||
}
|
||||
for (TabPlayer p : tab.getPlayers()) {
|
||||
usedPlaceholders.addAll(tab.getPlaceholderManager().getUsedPlaceholderIdentifiersRecursive(p.getProperty("tagprefix").getCurrentRawValue(),
|
||||
p.getProperty("customtagname").getCurrentRawValue(), p.getProperty("tagsuffix").getCurrentRawValue(),
|
||||
p.getProperty("abovename").getCurrentRawValue(), p.getProperty("belowname").getCurrentRawValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabFeature getFeatureType() {
|
||||
return TabFeature.NAMETAGX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getTeamVisibility(TabPlayer p, TabPlayer viewer) {
|
||||
//only visible if player is on boat & config option is enabled and player is not invisible (1.8 bug) or feature is disabled
|
||||
return (playersOnBoats.contains(p) && !invisiblePlayers.contains(p.getName())) || isDisabledWorld(disabledUnlimitedWorlds, p.getWorldName());
|
||||
}
|
||||
}
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.cpu.TabFeature;
|
||||
import me.neznamy.tab.shared.cpu.UsageType;
|
||||
import me.neznamy.tab.shared.features.types.packet.RawPacketListener;
|
||||
|
||||
/**
|
||||
* The packet listening part for securing proper functionality of armor stands
|
||||
* Bukkit events are too unreliable and delayed/ahead which causes desync
|
||||
*/
|
||||
public class PacketListener implements RawPacketListener {
|
||||
|
||||
//main feature
|
||||
private NameTagX nameTagX;
|
||||
|
||||
//nms storage
|
||||
private NMSStorage nms;
|
||||
|
||||
//tab instance
|
||||
private TAB tab;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
* @param nameTagX - main feature
|
||||
* @param nms - nms storage
|
||||
* @param tab - tab instance
|
||||
*/
|
||||
public PacketListener(NameTagX nameTagX, NMSStorage nms, TAB tab) {
|
||||
this.nameTagX = nameTagX;
|
||||
this.nms = nms;
|
||||
this.tab = tab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onPacketReceive(TabPlayer sender, Object packet) throws Throwable {
|
||||
if (sender.getVersion().getMinorVersion() == 8 && nms.PacketPlayInUseEntity.isInstance(packet)) {
|
||||
int entityId = nms.PacketPlayInUseEntity_ENTITY.getInt(packet);
|
||||
TabPlayer attacked = null;
|
||||
for (TabPlayer all : tab.getPlayers()) {
|
||||
if (!all.isLoaded()) continue;
|
||||
if (all.getArmorStandManager().hasArmorStandWithID(entityId)) {
|
||||
attacked = all;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attacked != null && attacked != sender) {
|
||||
nms.PacketPlayInUseEntity_ENTITY.set(packet, ((Player) attacked.getPlayer()).getEntityId());
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws Throwable {
|
||||
if (receiver.getVersion().getMinorVersion() < 8) return;
|
||||
//using bukkit player to check world due to old data on world change due to asynchronous processing & world name changing
|
||||
String world = ((Player)receiver.getPlayer()).getWorld().getName();
|
||||
if (!receiver.isLoaded() || nameTagX.isDisabledWorld(world) || nameTagX.isDisabledWorld(nameTagX.disabledUnlimitedWorlds, world)) return;
|
||||
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
|
||||
onEntityMove(receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
|
||||
}
|
||||
if (nms.PacketPlayOutEntityTeleport.isInstance(packet)) {
|
||||
onEntityMove(receiver, nms.PacketPlayOutEntityTeleport_ENTITYID.getInt(packet));
|
||||
}
|
||||
if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
|
||||
onEntitySpawn(receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
|
||||
}
|
||||
if (nms.PacketPlayOutEntityDestroy.isInstance(packet)) {
|
||||
onEntityDestroy(receiver, (int[]) nms.PacketPlayOutEntityDestroy_ENTITIES.get(packet));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes entity move packet
|
||||
* @param receiver - packet receiver
|
||||
* @param entityId - entity that moved
|
||||
*/
|
||||
public void onEntityMove(TabPlayer receiver, int entityId) {
|
||||
TabPlayer pl = nameTagX.entityIdMap.get(entityId);
|
||||
List<Entity> vehicleList;
|
||||
if (pl != null) {
|
||||
//player moved
|
||||
tab.getCPUManager().runMeasuredTask("processing EntityMove", getFeatureType(), UsageType.PACKET_ENTITY_MOVE, () -> pl.getArmorStandManager().teleport(receiver));
|
||||
} else if ((vehicleList = nameTagX.vehicles.get(entityId)) != null){
|
||||
//a vehicle carrying something moved
|
||||
for (Entity entity : vehicleList) {
|
||||
TabPlayer passenger = nameTagX.entityIdMap.get(entity.getEntityId());
|
||||
if (passenger != null && passenger.getArmorStandManager() != null) {
|
||||
tab.getCPUManager().runMeasuredTask("processing EntityMove", getFeatureType(), UsageType.PACKET_ENTITY_MOVE, () -> passenger.getArmorStandManager().teleport(receiver));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onEntitySpawn(TabPlayer receiver, int entityId) {
|
||||
TabPlayer spawnedPlayer = nameTagX.entityIdMap.get(entityId);
|
||||
if (spawnedPlayer != null && spawnedPlayer.isLoaded()) {
|
||||
tab.getCPUManager().runMeasuredTask("processing NamedEntitySpawn", getFeatureType(), UsageType.PACKET_NAMED_ENTITY_SPAWN, () -> spawnedPlayer.getArmorStandManager().spawn(receiver));
|
||||
}
|
||||
}
|
||||
|
||||
public void onEntityDestroy(TabPlayer receiver, int[] entities) {
|
||||
for (int id : entities) {
|
||||
TabPlayer despawnedPlayer = nameTagX.entityIdMap.get(id);
|
||||
if (despawnedPlayer != null && despawnedPlayer.isLoaded())
|
||||
tab.getCPUManager().runMeasuredTask("processing EntityDestroy", getFeatureType(), UsageType.PACKET_ENTITY_DESTROY, () -> despawnedPlayer.getArmorStandManager().destroy(receiver));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabFeature getFeatureType() {
|
||||
return TabFeature.NAMETAGX;
|
||||
}
|
||||
}
|
||||
+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");
|
||||
}
|
||||
}
|
||||
@@ -1,714 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.Main;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherRegistry;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class NMSStorage {
|
||||
|
||||
//instance of this class
|
||||
private static NMSStorage instance;
|
||||
|
||||
//field mapping changes in thermos
|
||||
private Map<String, String> thermosFieldMappings = new HashMap<String, String>();
|
||||
|
||||
//data watcher registry
|
||||
public DataWatcherRegistry dataWatcherRegistry;
|
||||
|
||||
//server package, such as "v1_16_R3"
|
||||
private String serverPackage;
|
||||
|
||||
//server minor version such as "16"
|
||||
public int minorVersion;
|
||||
|
||||
public Field PING;
|
||||
public Field PLAYER_CONNECTION;
|
||||
public Field NETWORK_MANAGER;
|
||||
public Field CHANNEL;
|
||||
public Method getHandle;
|
||||
public Method getProfile;
|
||||
public Method sendPacket;
|
||||
|
||||
//chat
|
||||
public Class<Enum> EnumChatFormat;
|
||||
public Class<?> IChatBaseComponent;
|
||||
public Class<?> ChatBaseComponent;
|
||||
public Class<?> ChatSerializer;
|
||||
public Class<?> EnumClickAction;
|
||||
public Class<?> EnumHoverAction;
|
||||
public Class<?> ChatModifier;
|
||||
public Class<?> ChatComponentText;
|
||||
public Class<?> ChatClickable;
|
||||
public Class<?> ChatHoverable;
|
||||
public Class<?> ChatHexColor;
|
||||
public Constructor<?> newChatComponentText;
|
||||
public Constructor<?> newChatClickable;
|
||||
public Constructor<?> newChatHoverable;
|
||||
public Constructor<?> newChatModifier;
|
||||
public Field ChatBaseComponent_extra;
|
||||
public Field ChatBaseComponent_modifier;
|
||||
public Field ChatClickable_action;
|
||||
public Field ChatClickable_value;
|
||||
public Field ChatHexColor_name;
|
||||
public Field ChatHexColor_rgb;
|
||||
public Field ChatHoverable_action;
|
||||
public Field ChatHoverable_value;
|
||||
public Field ChatModifier_color;
|
||||
public Field ChatModifier_bold;
|
||||
public Field ChatModifier_italic;
|
||||
public Field ChatModifier_underlined;
|
||||
public Field ChatModifier_strikethrough;
|
||||
public Field ChatModifier_obfuscated;
|
||||
public Field ChatModifier_clickEvent;
|
||||
public Field ChatModifier_hoverEvent;
|
||||
public Field ChatComponentText_text;
|
||||
public Method ChatSerializer_DESERIALIZE;
|
||||
public Method EnumClickAction_a;
|
||||
public Method EnumHoverAction_a;
|
||||
public Method ChatHexColor_ofInt;
|
||||
public Method ChatHexColor_ofString;
|
||||
public Method ChatComponentText_addSibling;
|
||||
|
||||
//PacketPlayOutBoss
|
||||
public Class<?> PacketPlayOutBoss;
|
||||
public Class<Enum> BarColor;
|
||||
public Class<Enum> BarStyle;
|
||||
public Class<Enum> PacketPlayOutBoss_Action;
|
||||
public Constructor<?> newPacketPlayOutBoss;
|
||||
public Field PacketPlayOutBoss_UUID;
|
||||
public Field PacketPlayOutBoss_ACTION;
|
||||
public Field PacketPlayOutBoss_NAME;
|
||||
public Field PacketPlayOutBoss_PROGRESS;
|
||||
public Field PacketPlayOutBoss_COLOR;
|
||||
public Field PacketPlayOutBoss_STYLE;
|
||||
public Field PacketPlayOutBoss_DARKEN_SKY;
|
||||
public Field PacketPlayOutBoss_PLAY_MUSIC;
|
||||
public Field PacketPlayOutBoss_CREATE_FOG;
|
||||
|
||||
//PacketPlayOutChat
|
||||
public Class<?> PacketPlayOutChat;
|
||||
public Class<Enum> ChatMessageType;
|
||||
public Constructor<?> newPacketPlayOutChat;
|
||||
public Field PacketPlayOutChat_MESSAGE;
|
||||
public Field PacketPlayOutChat_POSITION;
|
||||
public Field PacketPlayOutChat_SENDER;
|
||||
|
||||
//PacketPlayOutPlayerListHeaderFooter
|
||||
public Class<?> PacketPlayOutPlayerListHeaderFooter;
|
||||
public Constructor<?> newPacketPlayOutPlayerListHeaderFooter;
|
||||
public Field PacketPlayOutPlayerListHeaderFooter_HEADER;
|
||||
public Field PacketPlayOutPlayerListHeaderFooter_FOOTER;
|
||||
|
||||
//PacketPlayOutScoreboardDisplayObjective
|
||||
public Class<?> PacketPlayOutScoreboardDisplayObjective;
|
||||
public Constructor<?> newPacketPlayOutScoreboardDisplayObjective;
|
||||
public Field PacketPlayOutScoreboardDisplayObjective_POSITION;
|
||||
public Field PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME;
|
||||
|
||||
//PacketPlayOutScoreboardObjective
|
||||
public Class<?> PacketPlayOutScoreboardObjective;
|
||||
public Class<Enum> EnumScoreboardHealthDisplay;
|
||||
public Constructor<?> newPacketPlayOutScoreboardObjective;
|
||||
public Field PacketPlayOutScoreboardObjective_OBJECTIVENAME;
|
||||
public Field PacketPlayOutScoreboardObjective_DISPLAYNAME;
|
||||
public Field PacketPlayOutScoreboardObjective_RENDERTYPE;
|
||||
public Field PacketPlayOutScoreboardObjective_METHOD;
|
||||
|
||||
//PacketPlayOutScoreboardScore
|
||||
public Class<?> PacketPlayOutScoreboardScore;
|
||||
public Class<Enum> EnumScoreboardAction;
|
||||
public Constructor<?> newPacketPlayOutScoreboardScore0;
|
||||
public Constructor<?> newPacketPlayOutScoreboardScore_String;
|
||||
public Constructor<?> newPacketPlayOutScoreboardScore_1_13;
|
||||
public Field PacketPlayOutScoreboardScore_PLAYER;
|
||||
public Field PacketPlayOutScoreboardScore_OBJECTIVENAME;
|
||||
public Field PacketPlayOutScoreboardScore_SCORE;
|
||||
public Field PacketPlayOutScoreboardScore_ACTION;
|
||||
|
||||
//PacketPlayOutScoreboardTeam
|
||||
public Class<?> PacketPlayOutScoreboardTeam;
|
||||
public Constructor<?> newPacketPlayOutScoreboardTeam;
|
||||
public Field PacketPlayOutScoreboardTeam_NAME;
|
||||
public Field PacketPlayOutScoreboardTeam_DISPLAYNAME;
|
||||
public Field PacketPlayOutScoreboardTeam_PREFIX;
|
||||
public Field PacketPlayOutScoreboardTeam_SUFFIX;
|
||||
public Field PacketPlayOutScoreboardTeam_VISIBILITY; //1.8+
|
||||
public Field PacketPlayOutScoreboardTeam_CHATFORMAT; //1.13+
|
||||
public Field PacketPlayOutScoreboardTeam_COLLISION; //1.9+
|
||||
public Field PacketPlayOutScoreboardTeam_PLAYERS;
|
||||
public Field PacketPlayOutScoreboardTeam_ACTION;
|
||||
public Field PacketPlayOutScoreboardTeam_SIGNATURE;
|
||||
|
||||
//PacketPlayOutPlayerInfo
|
||||
public Class<?> PacketPlayOutPlayerInfo;
|
||||
public Class<Enum> EnumGamemode;
|
||||
public Class<Enum> EnumPlayerInfoAction;
|
||||
public Class<?> PlayerInfoData;
|
||||
public Constructor<?> newPacketPlayOutPlayerInfo;
|
||||
public Constructor<?> newPlayerInfoData;
|
||||
|
||||
public Class<?> GameProfile;
|
||||
public Constructor<?> newGameProfile;
|
||||
public Field GameProfile_ID;
|
||||
public Field GameProfile_NAME;
|
||||
public Field GameProfile_PROPERTIES;
|
||||
public Class<?> PropertyMap;
|
||||
public Method PropertyMap_putAll;
|
||||
|
||||
public Field PacketPlayOutPlayerInfo_ACTION;
|
||||
public Field PacketPlayOutPlayerInfo_PLAYERS;
|
||||
|
||||
public Field PlayerInfoData_PING;
|
||||
public Field PlayerInfoData_GAMEMODE;
|
||||
public Field PlayerInfoData_PROFILE;
|
||||
public Field PlayerInfoData_LISTNAME;
|
||||
|
||||
//PacketPlayOutTitle
|
||||
public Class<?> PacketPlayOutTitle;
|
||||
public Constructor<?> newPacketPlayOutTitle;
|
||||
public Class<Enum> EnumTitleAction;
|
||||
|
||||
public Class<?> PacketPlayOutEntityDestroy;
|
||||
public Constructor<?> newPacketPlayOutEntityDestroy;
|
||||
public Field PacketPlayOutEntityDestroy_ENTITIES;
|
||||
|
||||
public Class<?> PacketPlayInUseEntity;
|
||||
public Field PacketPlayInUseEntity_ENTITY;
|
||||
public Field PacketPlayInUseEntity_ACTION;
|
||||
|
||||
public Class<?> PacketPlayOutNamedEntitySpawn;
|
||||
public Field PacketPlayOutNamedEntitySpawn_ENTITYID;
|
||||
|
||||
public Class<?> PacketPlayOutEntity;
|
||||
public Class<?> PacketPlayOutEntityLook;
|
||||
public Field PacketPlayOutEntity_ENTITYID;
|
||||
|
||||
public Class<?> PacketPlayOutEntityTeleport;
|
||||
public Constructor<?> newPacketPlayOutEntityTeleport;
|
||||
public Field PacketPlayOutEntityTeleport_ENTITYID;
|
||||
public Field PacketPlayOutEntityTeleport_X;
|
||||
public Field PacketPlayOutEntityTeleport_Y;
|
||||
public Field PacketPlayOutEntityTeleport_Z;
|
||||
public Field PacketPlayOutEntityTeleport_YAW;
|
||||
public Field PacketPlayOutEntityTeleport_PITCH;
|
||||
|
||||
public Class<?> PacketPlayOutEntityMetadata;
|
||||
public Constructor<?> newPacketPlayOutEntityMetadata;
|
||||
public Field PacketPlayOutEntityMetadata_LIST;
|
||||
|
||||
public Class<?> PacketPlayOutSpawnEntityLiving;
|
||||
public Constructor<?> newPacketPlayOutSpawnEntityLiving;
|
||||
public Field PacketPlayOutSpawnEntityLiving_ENTITYID;
|
||||
public Field PacketPlayOutSpawnEntityLiving_UUID;
|
||||
public Field PacketPlayOutSpawnEntityLiving_ENTITYTYPE;
|
||||
public Field PacketPlayOutSpawnEntityLiving_X;
|
||||
public Field PacketPlayOutSpawnEntityLiving_Y;
|
||||
public Field PacketPlayOutSpawnEntityLiving_Z;
|
||||
public Field PacketPlayOutSpawnEntityLiving_YAW;
|
||||
public Field PacketPlayOutSpawnEntityLiving_PITCH;
|
||||
public Field PacketPlayOutSpawnEntityLiving_DATAWATCHER;
|
||||
|
||||
//DataWatcher
|
||||
public Class<?> DataWatcher;
|
||||
public Constructor<?> newDataWatcher;
|
||||
public Method DataWatcher_REGISTER;
|
||||
|
||||
public Class<?> DataWatcherItem;
|
||||
public Constructor<?> newDataWatcherItem;
|
||||
public Field DataWatcherItem_TYPE;
|
||||
public Field DataWatcherItem_VALUE;
|
||||
|
||||
public Class<?> DataWatcherObject;
|
||||
public Constructor<?> newDataWatcherObject;
|
||||
public Field DataWatcherObject_SLOT;
|
||||
public Field DataWatcherObject_SERIALIZER;
|
||||
|
||||
public Class<?> DataWatcherRegistry;
|
||||
|
||||
/**
|
||||
* Creates new instance, initializes required NMS classes and fields
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
public NMSStorage() throws Exception {
|
||||
serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
loadThermosMappings();
|
||||
initializeClasses();
|
||||
initializeConstructors();
|
||||
initializeFields();
|
||||
initializeMethods();
|
||||
dataWatcherRegistry = new DataWatcherRegistry(this, DataWatcherRegistry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets new instance
|
||||
* @param instance - new instance
|
||||
*/
|
||||
public static void setInstance(NMSStorage instance) {
|
||||
NMSStorage.instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns instance
|
||||
* @return instance
|
||||
*/
|
||||
public static NMSStorage getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts thermos mapping changes into map
|
||||
*/
|
||||
private void loadThermosMappings() {
|
||||
thermosFieldMappings.put("ping", "field_71138_i");
|
||||
thermosFieldMappings.put("playerConnection", "field_71135_a");
|
||||
thermosFieldMappings.put("networkManager", "field_147371_a");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes required NMS classes
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
public void initializeClasses() throws Exception {
|
||||
DataWatcher = getNMSClass("DataWatcher");
|
||||
DataWatcherItem = getNMSClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
|
||||
EnumChatFormat = (Class<Enum>) getNMSClass("EnumChatFormat");
|
||||
PacketPlayInUseEntity = getNMSClass("PacketPlayInUseEntity", "Packet7UseEntity");
|
||||
PacketPlayOutChat = getNMSClass("PacketPlayOutChat", "Packet3Chat");
|
||||
PacketPlayOutEntity = getNMSClass("PacketPlayOutEntity", "Packet30Entity");
|
||||
PacketPlayOutEntityDestroy = getNMSClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
|
||||
PacketPlayOutEntityLook = getNMSClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
|
||||
PacketPlayOutEntityMetadata = getNMSClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
|
||||
PacketPlayOutEntityTeleport = getNMSClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
|
||||
PacketPlayOutNamedEntitySpawn = getNMSClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
|
||||
PacketPlayOutScoreboardDisplayObjective = getNMSClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
|
||||
PacketPlayOutScoreboardObjective = getNMSClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
|
||||
PacketPlayOutScoreboardScore = getNMSClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
|
||||
PacketPlayOutScoreboardTeam = getNMSClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
|
||||
PacketPlayOutSpawnEntityLiving = getNMSClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
|
||||
|
||||
if (minorVersion >= 7) {
|
||||
ChatBaseComponent = getNMSClass("ChatBaseComponent");
|
||||
ChatClickable = getNMSClass("ChatClickable");
|
||||
ChatComponentText = getNMSClass("ChatComponentText");
|
||||
ChatHoverable = getNMSClass("ChatHoverable");
|
||||
ChatModifier = getNMSClass("ChatModifier");
|
||||
ChatSerializer = getNMSClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
|
||||
EnumClickAction = getNMSClass("ChatClickable$EnumClickAction", "EnumClickAction");
|
||||
EnumHoverAction = getNMSClass("ChatHoverable$EnumHoverAction", "EnumHoverAction");
|
||||
IChatBaseComponent = getNMSClass("IChatBaseComponent");
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
GameProfile = Class.forName("com.mojang.authlib.GameProfile");
|
||||
PropertyMap = Class.forName("com.mojang.authlib.properties.PropertyMap");
|
||||
PacketPlayOutPlayerInfo = getNMSClass("PacketPlayOutPlayerInfo");
|
||||
PacketPlayOutPlayerListHeaderFooter = getNMSClass("PacketPlayOutPlayerListHeaderFooter");
|
||||
PacketPlayOutTitle = getNMSClass("PacketPlayOutTitle");
|
||||
EnumPlayerInfoAction = (Class<Enum>) getNMSClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
|
||||
PlayerInfoData = getNMSClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
|
||||
EnumScoreboardHealthDisplay = (Class<Enum>) getNMSClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
|
||||
EnumTitleAction = (Class<Enum>) getNMSClass("PacketPlayOutTitle$EnumTitleAction", "EnumTitleAction");
|
||||
EnumGamemode = (Class<Enum>) getNMSClass("EnumGamemode", "WorldSettings$EnumGamemode");
|
||||
EnumScoreboardAction = (Class<Enum>) getNMSClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcherObject = getNMSClass("DataWatcherObject");
|
||||
DataWatcherRegistry = getNMSClass("DataWatcherRegistry");
|
||||
PacketPlayOutBoss = getNMSClass("PacketPlayOutBoss");
|
||||
BarColor = (Class<Enum>) getNMSClass("BossBattle$BarColor");
|
||||
BarStyle = (Class<Enum>) getNMSClass("BossBattle$BarStyle");
|
||||
PacketPlayOutBoss_Action = (Class<Enum>) getNMSClass("PacketPlayOutBoss$Action");
|
||||
}
|
||||
if (minorVersion >= 12) {
|
||||
ChatMessageType = (Class<Enum>) getNMSClass("ChatMessageType");
|
||||
}
|
||||
if (minorVersion >= 16) {
|
||||
ChatHexColor = getNMSClass("ChatHexColor");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes required NMS constructors
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
public void initializeConstructors() throws Exception {
|
||||
newDataWatcher = DataWatcher.getConstructors()[0];
|
||||
newDataWatcherItem = DataWatcherItem.getConstructors()[0];
|
||||
newPacketPlayOutChat = PacketPlayOutChat.getConstructor();
|
||||
newPacketPlayOutScoreboardDisplayObjective = PacketPlayOutScoreboardDisplayObjective.getConstructor();
|
||||
newPacketPlayOutScoreboardObjective = PacketPlayOutScoreboardObjective.getConstructor();
|
||||
newPacketPlayOutScoreboardTeam = PacketPlayOutScoreboardTeam.getConstructor();
|
||||
newPacketPlayOutEntityDestroy = PacketPlayOutEntityDestroy.getConstructor(int[].class);
|
||||
newPacketPlayOutEntityMetadata = PacketPlayOutEntityMetadata.getConstructor(int.class, DataWatcher, boolean.class);
|
||||
newPacketPlayOutEntityTeleport = PacketPlayOutEntityTeleport.getConstructor();
|
||||
newPacketPlayOutSpawnEntityLiving = PacketPlayOutSpawnEntityLiving.getConstructor();
|
||||
if (minorVersion >= 7) {
|
||||
newChatComponentText = ChatComponentText.getConstructor(String.class);
|
||||
newChatClickable = ChatClickable.getConstructor(EnumClickAction, String.class);
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
newGameProfile = GameProfile.getConstructor(UUID.class, String.class);
|
||||
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooter.getConstructor();
|
||||
newPacketPlayOutTitle = PacketPlayOutTitle.getConstructor(EnumTitleAction, IChatBaseComponent, int.class, int.class, int.class);
|
||||
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfo.getConstructor();
|
||||
try {
|
||||
newPlayerInfoData = PlayerInfoData.getConstructor(PacketPlayOutPlayerInfo, GameProfile, int.class, EnumGamemode, IChatBaseComponent);
|
||||
} catch (Exception e) {
|
||||
//1.8.8 paper
|
||||
newPlayerInfoData = PlayerInfoData.getConstructor(GameProfile, int.class, EnumGamemode, IChatBaseComponent);
|
||||
}
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
newDataWatcherObject = DataWatcherObject.getConstructors()[0];
|
||||
newPacketPlayOutBoss = PacketPlayOutBoss.getConstructor();
|
||||
}
|
||||
if (minorVersion >= 13) {
|
||||
newPacketPlayOutScoreboardScore_1_13 = PacketPlayOutScoreboardScore.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
|
||||
} else {
|
||||
newPacketPlayOutScoreboardScore0 = PacketPlayOutScoreboardScore.getConstructor();
|
||||
newPacketPlayOutScoreboardScore_String = PacketPlayOutScoreboardScore.getConstructor(String.class);
|
||||
}
|
||||
if (minorVersion >= 16) {
|
||||
newChatHoverable = ChatHoverable.getConstructor(EnumHoverAction, Object.class);
|
||||
newChatModifier = getConstructor(ChatModifier, 10);
|
||||
} else if (minorVersion >= 7) {
|
||||
newChatHoverable = ChatHoverable.getConstructor(EnumHoverAction, IChatBaseComponent);
|
||||
newChatModifier = ChatModifier.getConstructor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes required NMS fields
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
public void initializeFields() throws Exception {
|
||||
PING = getField(getNMSClass("EntityPlayer"), "ping");
|
||||
PLAYER_CONNECTION = getField(getNMSClass("EntityPlayer"), "playerConnection");
|
||||
NETWORK_MANAGER = getField(PLAYER_CONNECTION.getType(), "networkManager");
|
||||
|
||||
PacketPlayOutScoreboardDisplayObjective_POSITION = getField(PacketPlayOutScoreboardDisplayObjective, "a");
|
||||
PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME = getField(PacketPlayOutScoreboardDisplayObjective, "b");
|
||||
|
||||
PacketPlayOutScoreboardObjective_OBJECTIVENAME = getField(PacketPlayOutScoreboardObjective, "a");
|
||||
PacketPlayOutScoreboardObjective_DISPLAYNAME = getField(PacketPlayOutScoreboardObjective, "b");
|
||||
|
||||
PacketPlayOutScoreboardScore_PLAYER = getField(PacketPlayOutScoreboardScore, "a");
|
||||
PacketPlayOutScoreboardScore_OBJECTIVENAME = getField(PacketPlayOutScoreboardScore, "b");
|
||||
PacketPlayOutScoreboardScore_SCORE = getField(PacketPlayOutScoreboardScore, "c");
|
||||
PacketPlayOutScoreboardScore_ACTION = getField(PacketPlayOutScoreboardScore, "d");
|
||||
|
||||
PacketPlayOutScoreboardTeam_NAME = getField(PacketPlayOutScoreboardTeam, "a");
|
||||
PacketPlayOutScoreboardTeam_DISPLAYNAME = getField(PacketPlayOutScoreboardTeam, "b");
|
||||
PacketPlayOutScoreboardTeam_PREFIX = getField(PacketPlayOutScoreboardTeam, "c");
|
||||
PacketPlayOutScoreboardTeam_SUFFIX = getField(PacketPlayOutScoreboardTeam, "d");
|
||||
|
||||
PacketPlayOutEntityTeleport_ENTITYID = getField(PacketPlayOutEntityTeleport, "a");
|
||||
PacketPlayOutEntityTeleport_X = getField(PacketPlayOutEntityTeleport, "b");
|
||||
PacketPlayOutEntityTeleport_Y = getField(PacketPlayOutEntityTeleport, "c");
|
||||
PacketPlayOutEntityTeleport_Z = getField(PacketPlayOutEntityTeleport, "d");
|
||||
PacketPlayOutEntityTeleport_YAW = getField(PacketPlayOutEntityTeleport, "e");
|
||||
PacketPlayOutEntityTeleport_PITCH = getField(PacketPlayOutEntityTeleport, "f");
|
||||
|
||||
PacketPlayOutEntity_ENTITYID = getField(PacketPlayOutEntity, "a");
|
||||
PacketPlayOutEntityMetadata_LIST = getField(PacketPlayOutEntityMetadata, "b");
|
||||
|
||||
PacketPlayOutSpawnEntityLiving_ENTITYID = getField(PacketPlayOutSpawnEntityLiving, "a");
|
||||
|
||||
PacketPlayOutEntityDestroy_ENTITIES = getField(PacketPlayOutEntityDestroy, "a");
|
||||
PacketPlayOutNamedEntitySpawn_ENTITYID = getField(PacketPlayOutNamedEntitySpawn, "a");
|
||||
|
||||
if (minorVersion >= 7) {
|
||||
PacketPlayOutChat_MESSAGE = getField(PacketPlayOutChat, "a");
|
||||
ChatBaseComponent_modifier = getFields(ChatBaseComponent, ChatModifier).get(0);
|
||||
ChatComponentText_text = getFields(ChatComponentText, String.class).get(0);
|
||||
ChatClickable_action = getField(ChatClickable, "a");
|
||||
ChatClickable_value = getField(ChatClickable, "b");
|
||||
PacketPlayInUseEntity_ENTITY = getField(PacketPlayInUseEntity, "a");
|
||||
try {
|
||||
//1.14.4+
|
||||
ChatBaseComponent_extra = getField(ChatBaseComponent, "siblings");
|
||||
ChatModifier_color = getField(ChatModifier, "color");
|
||||
ChatModifier_bold = getField(ChatModifier, "bold");
|
||||
ChatModifier_italic = getField(ChatModifier, "italic");
|
||||
ChatModifier_underlined = getField(ChatModifier, "underlined");
|
||||
ChatModifier_strikethrough = getField(ChatModifier, "strikethrough");
|
||||
ChatModifier_obfuscated = getField(ChatModifier, "obfuscated");
|
||||
ChatModifier_clickEvent = getField(ChatModifier, "clickEvent");
|
||||
ChatModifier_hoverEvent = getField(ChatModifier, "hoverEvent");
|
||||
} catch (NoSuchFieldException e) {
|
||||
//1.14.3-
|
||||
ChatBaseComponent_extra = getField(ChatBaseComponent, "a");
|
||||
ChatModifier_color = getField(ChatModifier, "b");
|
||||
ChatModifier_bold = getField(ChatModifier, "c");
|
||||
ChatModifier_italic = getField(ChatModifier, "d");
|
||||
ChatModifier_underlined = getField(ChatModifier, "e");
|
||||
ChatModifier_strikethrough = getField(ChatModifier, "f");
|
||||
ChatModifier_obfuscated = getField(ChatModifier, "g");
|
||||
ChatModifier_clickEvent = getField(ChatModifier, "h");
|
||||
ChatModifier_hoverEvent = getField(ChatModifier, "i");
|
||||
}
|
||||
} else {
|
||||
//1.6-
|
||||
(PacketPlayOutChat_MESSAGE = PacketPlayOutChat.getDeclaredField("message")).setAccessible(true);
|
||||
}
|
||||
|
||||
if (minorVersion >= 8) {
|
||||
//1.8+
|
||||
CHANNEL = getFields(getNMSClass("NetworkManager"), Channel.class).get(0);
|
||||
|
||||
PacketPlayOutChat_POSITION = getField(PacketPlayOutChat, "b");
|
||||
|
||||
PacketPlayOutScoreboardObjective_RENDERTYPE = getField(PacketPlayOutScoreboardObjective, "c");
|
||||
PacketPlayOutScoreboardObjective_METHOD = getField(PacketPlayOutScoreboardObjective, "d");
|
||||
|
||||
GameProfile_ID = getField(GameProfile, "id");
|
||||
GameProfile_NAME = getField(GameProfile, "name");
|
||||
GameProfile_PROPERTIES = getField(GameProfile, "properties");
|
||||
PacketPlayOutPlayerInfo_ACTION = getField(PacketPlayOutPlayerInfo, "a");
|
||||
PacketPlayOutPlayerInfo_PLAYERS = getField(PacketPlayOutPlayerInfo, "b");
|
||||
PlayerInfoData_PING = getField(PlayerInfoData, "b");
|
||||
PlayerInfoData_GAMEMODE = getField(PlayerInfoData, "c");
|
||||
PlayerInfoData_PROFILE = getField(PlayerInfoData, "d");
|
||||
PlayerInfoData_LISTNAME = getField(PlayerInfoData, "e");
|
||||
|
||||
List<Field> fields = getFields(PacketPlayOutPlayerListHeaderFooter, IChatBaseComponent);
|
||||
PacketPlayOutPlayerListHeaderFooter_HEADER = fields.get(0);
|
||||
PacketPlayOutPlayerListHeaderFooter_FOOTER = fields.get(1);
|
||||
} else {
|
||||
//1.7-
|
||||
PacketPlayOutScoreboardObjective_METHOD = getField(PacketPlayOutScoreboardObjective, "c");
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
//1.9+
|
||||
DataWatcherItem_TYPE = getField(DataWatcherItem, "a");
|
||||
DataWatcherItem_VALUE = getField(DataWatcherItem, "b");
|
||||
DataWatcherObject_SLOT = getField(DataWatcherObject, "a");
|
||||
DataWatcherObject_SERIALIZER = getField(DataWatcherObject, "b");
|
||||
|
||||
PacketPlayOutBoss_UUID = getField(PacketPlayOutBoss, "a");
|
||||
PacketPlayOutBoss_ACTION = getField(PacketPlayOutBoss, "b");
|
||||
PacketPlayOutBoss_NAME = getField(PacketPlayOutBoss, "c");
|
||||
PacketPlayOutBoss_PROGRESS = getField(PacketPlayOutBoss, "d");
|
||||
PacketPlayOutBoss_COLOR = getField(PacketPlayOutBoss, "e");
|
||||
PacketPlayOutBoss_STYLE = getField(PacketPlayOutBoss, "f");
|
||||
PacketPlayOutBoss_DARKEN_SKY = getField(PacketPlayOutBoss, "g");
|
||||
PacketPlayOutBoss_PLAY_MUSIC = getField(PacketPlayOutBoss, "h");
|
||||
PacketPlayOutBoss_CREATE_FOG = getField(PacketPlayOutBoss, "i");
|
||||
|
||||
PacketPlayOutScoreboardTeam_VISIBILITY = getField(PacketPlayOutScoreboardTeam, "e");
|
||||
PacketPlayOutScoreboardTeam_COLLISION = getField(PacketPlayOutScoreboardTeam, "f");
|
||||
PacketPlayOutScoreboardTeam_PLAYERS = getField(PacketPlayOutScoreboardTeam, "h");
|
||||
PacketPlayOutScoreboardTeam_ACTION = getField(PacketPlayOutScoreboardTeam, "i");
|
||||
PacketPlayOutScoreboardTeam_SIGNATURE = getField(PacketPlayOutScoreboardTeam, "j");
|
||||
|
||||
PacketPlayOutSpawnEntityLiving_UUID = getField(PacketPlayOutSpawnEntityLiving, "b");
|
||||
PacketPlayOutSpawnEntityLiving_ENTITYTYPE = getField(PacketPlayOutSpawnEntityLiving, "c");
|
||||
PacketPlayOutSpawnEntityLiving_X = getField(PacketPlayOutSpawnEntityLiving, "d");
|
||||
PacketPlayOutSpawnEntityLiving_Y = getField(PacketPlayOutSpawnEntityLiving, "e");
|
||||
PacketPlayOutSpawnEntityLiving_Z = getField(PacketPlayOutSpawnEntityLiving, "f");
|
||||
PacketPlayOutSpawnEntityLiving_YAW = getField(PacketPlayOutSpawnEntityLiving, "j");
|
||||
PacketPlayOutSpawnEntityLiving_PITCH = getField(PacketPlayOutSpawnEntityLiving, "k");
|
||||
|
||||
PacketPlayInUseEntity_ACTION = getField(PacketPlayInUseEntity, "action");
|
||||
} else {
|
||||
//1.8-
|
||||
DataWatcherItem_TYPE = getField(DataWatcherItem, "b");
|
||||
DataWatcherItem_VALUE = getField(DataWatcherItem, "c");
|
||||
|
||||
PacketPlayOutSpawnEntityLiving_ENTITYTYPE = getField(PacketPlayOutSpawnEntityLiving, "b");
|
||||
PacketPlayOutSpawnEntityLiving_X = getField(PacketPlayOutSpawnEntityLiving, "c");
|
||||
PacketPlayOutSpawnEntityLiving_Y = getField(PacketPlayOutSpawnEntityLiving, "d");
|
||||
PacketPlayOutSpawnEntityLiving_Z = getField(PacketPlayOutSpawnEntityLiving, "e");
|
||||
PacketPlayOutSpawnEntityLiving_YAW = getField(PacketPlayOutSpawnEntityLiving, "i");
|
||||
PacketPlayOutSpawnEntityLiving_PITCH = getField(PacketPlayOutSpawnEntityLiving, "j");
|
||||
if (minorVersion == 8) {
|
||||
//1.8.x
|
||||
PacketPlayOutScoreboardTeam_VISIBILITY = getField(PacketPlayOutScoreboardTeam, "e");
|
||||
PacketPlayOutScoreboardTeam_PLAYERS = getField(PacketPlayOutScoreboardTeam, "g");
|
||||
PacketPlayOutScoreboardTeam_ACTION = getField(PacketPlayOutScoreboardTeam, "h");
|
||||
PacketPlayOutScoreboardTeam_SIGNATURE = getField(PacketPlayOutScoreboardTeam, "i");
|
||||
} else {
|
||||
//1.5.x - 1.7.x
|
||||
PacketPlayOutScoreboardTeam_PLAYERS = getField(PacketPlayOutScoreboardTeam, "e");
|
||||
PacketPlayOutScoreboardTeam_ACTION = getField(PacketPlayOutScoreboardTeam, "f");
|
||||
PacketPlayOutScoreboardTeam_SIGNATURE = getField(PacketPlayOutScoreboardTeam, "g");
|
||||
}
|
||||
}
|
||||
if (minorVersion >= 13) {
|
||||
//1.13+
|
||||
PacketPlayOutScoreboardTeam_CHATFORMAT = getField(PacketPlayOutScoreboardTeam, "g");
|
||||
}
|
||||
|
||||
if (minorVersion <= 14) {
|
||||
PacketPlayOutSpawnEntityLiving_DATAWATCHER = getFields(PacketPlayOutSpawnEntityLiving, DataWatcher).get(0);
|
||||
}
|
||||
|
||||
if (minorVersion >= 16) {
|
||||
//1.16+
|
||||
ChatHexColor_name = getField(ChatHexColor, "name");
|
||||
ChatHexColor_rgb = getField(ChatHexColor, "rgb");
|
||||
ChatHoverable_action = getField(ChatHoverable, "b");
|
||||
ChatHoverable_value = getField(ChatHoverable, "c");
|
||||
PacketPlayOutChat_SENDER = getField(PacketPlayOutChat, "c");
|
||||
} else if (minorVersion >= 7) {
|
||||
ChatHoverable_action = getField(ChatHoverable, "a");
|
||||
ChatHoverable_value = getField(ChatHoverable, "b");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes required NMS methods
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
public void initializeMethods() throws Exception {
|
||||
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
|
||||
sendPacket = getMethod(getNMSClass("PlayerConnection"), new String[]{"sendPacket", "func_147359_a"}, getNMSClass("Packet"));
|
||||
if (minorVersion >= 7) {
|
||||
ChatComponentText_addSibling = getMethod(ChatComponentText, new String[]{"addSibling", "a", "func_150257_a"}, IChatBaseComponent); //v1.7.R4+, v1.7.R3-
|
||||
ChatSerializer_DESERIALIZE = getMethod(ChatSerializer, new String[]{"a", "func_150699_a"}, String.class);
|
||||
EnumClickAction_a = getMethod(EnumClickAction, new String[]{"a", "func_150672_a"}, String.class);
|
||||
EnumHoverAction_a = getMethod(EnumHoverAction, new String[]{"a", "func_150684_a"}, String.class);
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
getProfile = getNMSClass("EntityHuman").getMethod("getProfile");
|
||||
for (Method m : PropertyMap.getMethods()) {
|
||||
if (m.getName().equals("putAll") && m.getParameterCount() == 1) PropertyMap_putAll = m;
|
||||
}
|
||||
if (PropertyMap_putAll == null) throw new IllegalStateException("putAll method not found");
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
//1.9+
|
||||
DataWatcher_REGISTER = DataWatcher.getMethod("register", DataWatcherObject, Object.class);
|
||||
} else {
|
||||
//1.7.x - 1.8.x
|
||||
DataWatcher_REGISTER = getMethod(DataWatcher, new String[]{"a", "func_75682_a"}, int.class, Object.class);
|
||||
}
|
||||
if (minorVersion >= 16) {
|
||||
ChatHexColor_ofInt = ChatHexColor.getMethod("a", int.class);
|
||||
ChatHexColor_ofString = ChatHexColor.getMethod("a", String.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method that prints all methods of class into console, including their return type, name and parameters
|
||||
* Useful for modded servers which code I can not access
|
||||
* @param clazz - class to show methods of
|
||||
*/
|
||||
public void showMethods(Class<?> clazz) {
|
||||
System.out.println("--- " + clazz.getSimpleName() + " ---");
|
||||
for (Method m : clazz.getMethods()) {
|
||||
System.out.println(m.getReturnType().getName() + " " + m.getName() + "(" + Arrays.toString(m.getParameterTypes()) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class with given potential names in same order
|
||||
* @param names - possible class names
|
||||
* @return class for specified name(s)
|
||||
* @throws ClassNotFoundException if class does not exist
|
||||
*/
|
||||
public Class<?> getNMSClass(String... names) throws ClassNotFoundException {
|
||||
for (String name : names) {
|
||||
try {
|
||||
return getNMSClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException("No class found with possible names " + names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class from given name
|
||||
* @param name - class name
|
||||
* @return class from given name
|
||||
* @throws ClassNotFoundException if class was not found
|
||||
*/
|
||||
public Class<?> getNMSClass(String name) throws ClassNotFoundException {
|
||||
try {
|
||||
return Class.forName("net.minecraft.server." + serverPackage + "." + name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
//modded server?
|
||||
return Main.class.getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
} catch (NullPointerException e) {
|
||||
//nested class in modded server
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns method with specified possible names and parameters. Throws exception if no such method was found
|
||||
* @param clazz - class to get method from
|
||||
* @param names - possible method names
|
||||
* @param parameterTypes - parameter types of the method
|
||||
* @return method with specified name and parameters
|
||||
* @throws NoSuchMethodException if no such method exists
|
||||
*/
|
||||
public Method getMethod(Class<?> clazz, String[] names, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
for (String name : names) {
|
||||
try {
|
||||
return clazz.getMethod(name, parameterTypes);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
throw new NoSuchMethodException("No method found with possible names " + Arrays.toString(names) + " in class " + clazz.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all fields of class with defined class type
|
||||
* @param clazz - class to check fields of
|
||||
* @param type - field type to check for
|
||||
* @return list of all fields with specified class type
|
||||
*/
|
||||
private List<Field> getFields(Class<?> clazz, Class<?> type){
|
||||
if (clazz == null) throw new IllegalArgumentException("Source class cannot be null");
|
||||
List<Field> list = new ArrayList<Field>();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
if (field.getType() == type) list.add(field);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns field with specified name and makes it accessible
|
||||
* @param clazz - class to get field from
|
||||
* @param name - field name
|
||||
* @return accessible field with defined name
|
||||
* @throws NoSuchFieldException if field was not found
|
||||
*/
|
||||
private Field getField(Class<?> clazz, String name) throws NoSuchFieldException {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (f.getName().equals(name) || (f.getName().split("_").length == 3 && f.getName().split("_")[2].equals(name))) {
|
||||
f.setAccessible(true);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
if (thermosFieldMappings.containsKey(name)) {
|
||||
return getField(clazz, thermosFieldMappings.get(name));
|
||||
}
|
||||
throw new NoSuchFieldException("Field \"" + name + "\" was not found in class " + clazz.getName());
|
||||
}
|
||||
|
||||
private Constructor<?> getConstructor(Class<?> clazz, int parameterCount) throws NoSuchMethodException {
|
||||
for (Constructor<?> c : clazz.getDeclaredConstructors()) {
|
||||
if (c.getParameterCount() == parameterCount) {
|
||||
c.setAccessible(true);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
throw new NoSuchMethodException("No constructor found in class " + clazz.getName() + " with " + parameterCount + " parameters");
|
||||
}
|
||||
}
|
||||
@@ -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,97 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcher class to make work with it much easier
|
||||
*/
|
||||
public class DataWatcher {
|
||||
|
||||
//datawatcher data
|
||||
private Map<Integer, DataWatcherItem> dataValues = new HashMap<Integer, DataWatcherItem>();
|
||||
|
||||
//a helper for easier data write
|
||||
private DataWatcherHelper helper = new DataWatcherHelper(this);
|
||||
|
||||
/**
|
||||
* Sets value into data values
|
||||
* @param type - type of value
|
||||
* @param value - value
|
||||
*/
|
||||
public void setValue(DataWatcherObject type, Object value){
|
||||
dataValues.put(type.position, new DataWatcherItem(type, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes value by position
|
||||
* @param position - position of value to remove
|
||||
*/
|
||||
public void removeValue(int position) {
|
||||
dataValues.remove(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns item with given position
|
||||
* @param position - position of item
|
||||
* @return item or null if not set
|
||||
*/
|
||||
public DataWatcherItem getItem(int position) {
|
||||
return dataValues.get(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns helper created by this instance
|
||||
* @return data write helper
|
||||
*/
|
||||
public DataWatcherHelper helper() {
|
||||
return helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the class into an instance of NMS.DataWatcher
|
||||
* @return an instance of NMS.DataWatcher with same data
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
public Object toNMS() throws Exception {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object nmsWatcher;
|
||||
if (nms.newDataWatcher.getParameterCount() == 1) {
|
||||
nmsWatcher = nms.newDataWatcher.newInstance(new Object[] {null});
|
||||
} else {
|
||||
nmsWatcher = nms.newDataWatcher.newInstance();
|
||||
}
|
||||
for (DataWatcherItem item : dataValues.values()) {
|
||||
Object position;
|
||||
if (nms.minorVersion >= 9) {
|
||||
position = nms.newDataWatcherObject.newInstance(item.type.position, item.type.classType);
|
||||
} else {
|
||||
position = item.type.position;
|
||||
}
|
||||
nms.DataWatcher_REGISTER.invoke(nmsWatcher, position, item.value);
|
||||
}
|
||||
return nmsWatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads NMS data watcher and returns and instance of this class with same data
|
||||
* @param nmsWatcher - NMS datawatcher to read
|
||||
* @return an instance of this class with same values
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static DataWatcher fromNMS(Object nmsWatcher) throws Exception{
|
||||
DataWatcher watcher = new DataWatcher();
|
||||
List<Object> items = (List<Object>) nmsWatcher.getClass().getMethod("c").invoke(nmsWatcher);
|
||||
if (items != null) {
|
||||
for (Object watchableObject : items) {
|
||||
DataWatcherItem w = DataWatcherItem.fromNMS(watchableObject);
|
||||
watcher.setValue(w.type, w.value);
|
||||
}
|
||||
}
|
||||
return watcher;
|
||||
}
|
||||
}
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitPacketBuilder;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.packets.IChatBaseComponent;
|
||||
|
||||
/**
|
||||
* A class to help assigning DataWatcher items as positions often change per-version
|
||||
*/
|
||||
public class DataWatcherHelper {
|
||||
|
||||
//position of armor stand flags
|
||||
private final int ARMOR_STAND_BYTEFLAGS_POSITION = getArmorStandFlagsPosition();
|
||||
|
||||
//original datawatcher to write to
|
||||
private DataWatcher data;
|
||||
|
||||
//data watcher registry
|
||||
private DataWatcherRegistry registry;
|
||||
|
||||
/**
|
||||
* Constructs new instance of this class with given parent
|
||||
* @param data - data to write to
|
||||
*/
|
||||
public DataWatcherHelper(DataWatcher data) {
|
||||
this.data = data;
|
||||
this.registry = NMSStorage.getInstance().dataWatcherRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns armor stand flags position based on server version
|
||||
* @return armor stand flags position based on server version
|
||||
*/
|
||||
private int getArmorStandFlagsPosition() {
|
||||
if (NMSStorage.getInstance().minorVersion >= 15) {
|
||||
//1.15.x, 1.16.x
|
||||
return 14;
|
||||
} else if (NMSStorage.getInstance().minorVersion >= 14) {
|
||||
//1.14.x
|
||||
return 13;
|
||||
} else if (NMSStorage.getInstance().minorVersion >= 10) {
|
||||
//1.10.x - 1.13.x
|
||||
return 11;
|
||||
} else {
|
||||
//1.8.1 - 1.9.x
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity byte flags
|
||||
* @param flags - flags to write
|
||||
*/
|
||||
public void setEntityFlags(byte flags) {
|
||||
data.setValue(new DataWatcherObject(0, registry.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(String customName, ProtocolVersion clientVersion) {
|
||||
if (NMSStorage.getInstance().minorVersion >= 13) {
|
||||
try {
|
||||
data.setValue(new DataWatcherObject(2, registry.Optional_IChatBaseComponent), Optional.ofNullable(((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to create component", e);
|
||||
}
|
||||
} else if (NMSStorage.getInstance().minorVersion >= 8){
|
||||
data.setValue(new DataWatcherObject(2, registry.String), customName);
|
||||
} else {
|
||||
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
|
||||
data.setValue(new DataWatcherObject(10, registry.String), cutName);
|
||||
if (NMSStorage.getInstance().minorVersion >= 6){
|
||||
data.setValue(new DataWatcherObject(10, registry.String), cutName);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(5, registry.String), cutName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes custom name visibility boolean
|
||||
* @param visible - if visible or not
|
||||
*/
|
||||
public void setCustomNameVisible(boolean visible) {
|
||||
if (NMSStorage.getInstance().minorVersion >= 9) {
|
||||
data.setValue(new DataWatcherObject(3, registry.Boolean), visible);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(3, registry.Byte), (byte)(visible?1:0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity health
|
||||
* @param health - health of entity
|
||||
*/
|
||||
public void setHealth(float health) {
|
||||
if (NMSStorage.getInstance().minorVersion >= 6) {
|
||||
data.setValue(new DataWatcherObject(6, registry.Float), health);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(16, registry.Integer), (int)health);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes armor stand flags
|
||||
* @param flags - flags to write
|
||||
*/
|
||||
public void setArmorStandFlags(byte flags) {
|
||||
data.setValue(new DataWatcherObject(ARMOR_STAND_BYTEFLAGS_POSITION, registry.Byte), flags);
|
||||
}
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
|
||||
public class DataWatcherItem {
|
||||
|
||||
//type of value (position + data type (1.9+))
|
||||
public DataWatcherObject type;
|
||||
|
||||
//actual data value
|
||||
public Object value;
|
||||
|
||||
/**
|
||||
* Constructs new instance of the object with given parameters
|
||||
* @param type - value type
|
||||
* @param value - value
|
||||
*/
|
||||
public DataWatcherItem(DataWatcherObject type, Object value){
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and instance of this class from given NMS item
|
||||
* @param nmsItem - NMS item
|
||||
* @return instance of this class with same data
|
||||
* @throws Exception - if something fails
|
||||
*/
|
||||
public static DataWatcherItem fromNMS(Object nmsItem) throws Exception {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
if (NMSStorage.getInstance().minorVersion >= 9) {
|
||||
Object nmsObject = nms.DataWatcherItem_TYPE.get(nmsItem);
|
||||
return new DataWatcherItem(new DataWatcherObject(nms.DataWatcherObject_SLOT.getInt(nmsObject), nms.DataWatcherObject_SERIALIZER.get(nmsObject)), nms.DataWatcherItem_VALUE.get(nmsItem));
|
||||
} else {
|
||||
return new DataWatcherItem(new DataWatcherObject(nms.DataWatcherItem_TYPE.getInt(nmsItem), null), nms.DataWatcherItem_VALUE.get(nmsItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcherObject class to make work with it much easier
|
||||
*/
|
||||
public class DataWatcherObject {
|
||||
|
||||
//position in datawatcher
|
||||
public int position;
|
||||
|
||||
//value class type used since 1.9
|
||||
public Object classType;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of this class with given parameters
|
||||
* @param position - position in datawatcher
|
||||
* @param classType - value class type
|
||||
*/
|
||||
public DataWatcherObject(int position, Object classType){
|
||||
this.position = position;
|
||||
this.classType = classType;
|
||||
}
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcherRegistry class to make work with it much easier
|
||||
*/
|
||||
public class DataWatcherRegistry {
|
||||
|
||||
//used registry types
|
||||
public Object Byte;
|
||||
public Object Integer;
|
||||
public Object Float;
|
||||
public Object String;
|
||||
public Object Optional_IChatBaseComponent;
|
||||
public Object Boolean;
|
||||
|
||||
/**
|
||||
* Initializes required NMS classes and fields
|
||||
*/
|
||||
public DataWatcherRegistry(NMSStorage nms, Class<?> registry) {
|
||||
if (nms.minorVersion >= 9) {
|
||||
Map<String, Object> fields = getStaticFields(registry);
|
||||
Byte = fields.get("a");
|
||||
Integer = fields.get("b");
|
||||
Float = fields.get("c");
|
||||
String = fields.get("d");
|
||||
if (nms.minorVersion >= 13) {
|
||||
Optional_IChatBaseComponent = fields.get("f");
|
||||
Boolean = fields.get("i");
|
||||
} else {
|
||||
Boolean = fields.get("h");
|
||||
}
|
||||
} else {
|
||||
Byte = 0;
|
||||
Integer = 2;
|
||||
Float = 3;
|
||||
String = 4;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets values of all static fields in a class
|
||||
* @param clazz class to return field values from
|
||||
* @return map of values
|
||||
*/
|
||||
private Map<String, Object> getStaticFields(Class<?> clazz){
|
||||
Map<String, Object> fields = new HashMap<String, Object>();
|
||||
if (clazz == null) return fields;
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
if (Modifier.isStatic(field.getModifiers())) {
|
||||
try {
|
||||
fields.put(field.getName(), field.get(null));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.permission;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.permission.PermissionPlugin;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
|
||||
/**
|
||||
* Vault permission hook
|
||||
*/
|
||||
public class Vault implements PermissionPlugin {
|
||||
|
||||
//permission plugi
|
||||
private Permission permission;
|
||||
|
||||
//vault version
|
||||
private String vaultVersion;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
* @param permission permission plugin implementation
|
||||
* @param vaultVersion vault version
|
||||
*/
|
||||
public Vault(Permission permission, String vaultVersion) {
|
||||
this.permission = permission;
|
||||
this.vaultVersion = vaultVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrimaryGroup(TabPlayer p) {
|
||||
if (getName().equals("SuperPerms")) return "<null>";
|
||||
return permission.getPrimaryGroup((Player) p.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getAllGroups(TabPlayer p) {
|
||||
if (getName().equals("SuperPerms")) return new String[] {"<null>"};
|
||||
return permission.getPlayerGroups((Player) p.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return permission.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "Vault " + vaultVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
}
|
||||
Loaded 100 of 573 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user