Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b38954694 | ||
|
|
9eb350df30 | ||
|
|
9070a6aadc | ||
|
|
c68e135c66 | ||
|
|
8693b4fe8f | ||
|
|
cb394007a4 | ||
|
|
cd92910281 | ||
|
|
5f77dd0adb | ||
|
|
4ef94d3c60 | ||
|
|
8e9b575c37 | ||
|
|
beab7d5a4f | ||
|
|
d2f5fc4d69 | ||
|
|
fb9a7f693b | ||
|
|
1d2d636270 | ||
|
|
37ec5ee442 | ||
|
|
5ed67cf5f8 | ||
|
|
76fc47d5bd | ||
|
|
aa856beee1 | ||
|
|
e6f2cc3eda | ||
|
|
02fd45587b | ||
|
|
7cef6eeb61 | ||
|
|
da919bc43b | ||
|
|
2b661d6c5b | ||
|
|
ee0ced63ef | ||
|
|
736dad5819 | ||
|
|
11f36e8a22 | ||
|
|
2cddf0b313 | ||
|
|
36a2d449b7 | ||
|
|
b6786456a2 | ||
|
|
76e8e1af6f | ||
|
|
ff55838a54 | ||
|
|
b14b6828cc | ||
|
|
be7fb67988 | ||
|
|
ae0be45d7d | ||
|
|
ba237d5dc4 | ||
|
|
bdf5cd8751 | ||
|
|
9a6d309840 | ||
|
|
9a6e92a58c | ||
|
|
1bc018918b | ||
|
|
f32f8f5cdd | ||
|
|
39585de3f0 | ||
|
|
9ce81fc68e | ||
|
|
0d56f0da98 | ||
|
|
14a3d8da12 | ||
|
|
e4fa99b865 | ||
|
|
96af2cb573 | ||
|
|
f53ec58b2f | ||
|
|
2ff30dda83 | ||
|
|
c0972f8faf | ||
|
|
9663e1aa7e | ||
|
|
d83a61b30a | ||
|
|
96d83da9b7 | ||
|
|
f1d881e0ba | ||
|
|
d1285c7e04 | ||
|
|
d4476e2d4c | ||
|
|
3124d51381 | ||
|
|
b6ae1a79d4 | ||
|
|
42d7b598bf | ||
|
|
052e828d5a | ||
|
|
55d4f8da6b | ||
|
|
1c5a4bbacc | ||
|
|
565d4bdeec | ||
|
|
3d9ef7d252 | ||
|
|
161bcbb236 | ||
|
|
c8af0ab5bc | ||
|
|
98e9c167c5 | ||
|
|
ed815c5767 | ||
|
|
f6e53ac91f | ||
|
|
3e5df50f9e | ||
|
|
1e28a93c5f | ||
|
|
717bdd3550 | ||
|
|
3bee514bb9 | ||
|
|
678d4b8db7 | ||
|
|
d76e5e7f9b | ||
|
|
9629192305 | ||
|
|
eef7debdba | ||
|
|
138d9f7f46 | ||
|
|
e4abd9ec2f | ||
|
|
8efce72e26 | ||
|
|
15ebca2d11 | ||
|
|
6583489523 | ||
|
|
5610d52813 | ||
|
|
618b61dd60 | ||
|
|
2de58f7a3b | ||
|
|
2bd94158f9 | ||
|
|
4e7ab43ac9 | ||
|
|
33529d0cfd | ||
|
|
a5b929bdf5 | ||
|
|
d10bd3358d | ||
|
|
ffe928fbe8 | ||
|
|
fd61739d96 | ||
|
|
e1602a5655 | ||
|
|
a936302fd2 | ||
|
|
7f0d4df6ca | ||
|
|
35ab0c67c5 | ||
|
|
090633140f | ||
|
|
8efe02c2ef | ||
|
|
2dbcaf029c | ||
|
|
ce758534bd | ||
|
|
15caf70170 | ||
|
|
c07c3a3a4f | ||
|
|
b9000af55e | ||
|
|
ed15ba29bb | ||
|
|
2fb8da022a | ||
|
|
ed65088cb8 | ||
|
|
f367542096 | ||
|
|
00e457542d | ||
|
|
b0c423b2dd | ||
|
|
eb5e3d637c | ||
|
|
4acd24b829 | ||
|
|
f8576834fe | ||
|
|
1e9888a8b5 | ||
|
|
d99b6643e9 | ||
|
|
0b98d98867 | ||
|
|
d040b0faf5 | ||
|
|
d53c20da56 | ||
|
|
fad035699f | ||
|
|
c7ab2b93a3 | ||
|
|
4b75144276 | ||
|
|
99edcf68c1 | ||
|
|
9b6f1ce955 | ||
|
|
b570020944 | ||
|
|
e91ba4ef79 | ||
|
|
a1fbefb13f | ||
|
|
340592d951 | ||
|
|
a1c0bf7dec | ||
|
|
a62f20469e | ||
|
|
3f1f78315b | ||
|
|
12d8bc75cd | ||
|
|
c1cf02ee9f | ||
|
|
7ac240af76 | ||
|
|
82c34a33b5 | ||
|
|
d2330337ef | ||
|
|
af38bd0383 | ||
|
|
c0a9ede377 | ||
|
|
555ba3eb73 | ||
|
|
6f0e757ad6 | ||
|
|
077b8fee96 | ||
|
|
643df47437 | ||
|
|
9e1ea4678d | ||
|
|
f3576b678b | ||
|
|
2b49bce998 | ||
|
|
896048ffff | ||
|
|
04cb14f790 | ||
|
|
bd22b0c08b | ||
|
|
95ce52bbeb | ||
|
|
6b411f5cce | ||
|
|
c15b0d178b | ||
|
|
f5ffb69675 | ||
|
|
24e59d970f | ||
|
|
4ece4b66cd | ||
|
|
7852d244bb | ||
|
|
d1c2487ba4 | ||
|
|
4ea789efcf | ||
|
|
43fc97c671 | ||
|
|
a20378d5cb | ||
|
|
7c0384c96b | ||
|
|
7d4419b300 | ||
|
|
393af9b5c5 | ||
|
|
559e423a1d | ||
|
|
a2d68e14e6 | ||
|
|
580b67d9ed | ||
|
|
080654f703 | ||
|
|
002a002749 | ||
|
|
7e888e46f9 | ||
|
|
199ca94acd | ||
|
|
67455bd47d | ||
|
|
fbb0e252c5 | ||
|
|
2566972ac7 | ||
|
|
9455294060 | ||
|
|
6870b42a1a | ||
|
|
32cd414a51 | ||
|
|
35f63b7075 | ||
|
|
f9097b11b6 | ||
|
|
dc5acdf729 | ||
|
|
e5fb25daee | ||
|
|
33b6867c02 | ||
|
|
d572973b60 | ||
|
|
3d240f0f7d | ||
|
|
3a4973d760 | ||
|
|
811c9716a2 | ||
|
|
771f6db89a | ||
|
|
97f515f573 | ||
|
|
c589dfb281 | ||
|
|
82efe5b4d1 | ||
|
|
765f593b88 | ||
|
|
21e5de25cb | ||
|
|
4333e44f6d | ||
|
|
7070fd886b | ||
|
|
89480a9018 | ||
|
|
19ce873037 | ||
|
|
977007b427 | ||
|
|
218c1c9d9a | ||
|
|
f358ac9d9c | ||
|
|
58fe2b4582 | ||
|
|
211624b1a9 | ||
|
|
6874369a48 | ||
|
|
f8c52d3577 | ||
|
|
376bf5314d | ||
|
|
2346df67a0 | ||
|
|
e63c7b43e1 | ||
|
|
578691fe8c | ||
|
|
189d8a82d5 | ||
|
|
9412d9cc28 | ||
|
|
75b2bfa14c | ||
|
|
7fcb5c8aa3 | ||
|
|
9827711111 | ||
|
|
16aecd65e6 | ||
|
|
c143ecc6ae | ||
|
|
2935f9e205 | ||
|
|
a6a33a79d4 | ||
|
|
82f9d8ddee | ||
|
|
daa6012b36 | ||
|
|
5001c82f23 | ||
|
|
59dbad9abf | ||
|
|
945c7cc7b8 | ||
|
|
3391f7646c | ||
|
|
88070aa2b2 | ||
|
|
c792296746 | ||
|
|
18a13f777d | ||
|
|
469529b324 | ||
|
|
4715a5ab9d | ||
|
|
be305162a6 | ||
|
|
7a1bc16e9d | ||
|
|
eb2c80deb8 | ||
|
|
0863b77bf0 | ||
|
|
769a698509 | ||
|
|
53870b2c81 | ||
|
|
e198a5df88 | ||
|
|
ac947f698b | ||
|
|
ecabd62b6b | ||
|
|
f350d200ac | ||
|
|
97d5a3f7ef | ||
|
|
f15f3efc3f | ||
|
|
7563b6b946 | ||
|
|
421c53e6be | ||
|
|
3db8666951 | ||
|
|
ecc6e01cf9 | ||
|
|
becf81db00 | ||
|
|
92f5b337c9 | ||
|
|
3ddf884c23 | ||
|
|
116117af55 | ||
|
|
0980bcfca5 | ||
|
|
20e2e8ded6 | ||
|
|
13af0b7b2b | ||
|
|
cbcbfdcbf1 | ||
|
|
38eb294667 | ||
|
|
71d16ff906 | ||
|
|
395058d771 | ||
|
|
d8af7c454c | ||
|
|
2f10084e7a | ||
|
|
5af387b32d | ||
|
|
4b7144f705 | ||
|
|
0fbbbb6485 | ||
|
|
f033d78785 | ||
|
|
2ea79eb57d | ||
|
|
226ee0db9d | ||
|
|
8a62f21e84 | ||
|
|
c353c39086 | ||
|
|
2b2c877ab2 | ||
|
|
016af4bfee | ||
|
|
b3d26d91de | ||
|
|
a7a7f409eb | ||
|
|
f44ccec712 | ||
|
|
1c0887733c | ||
|
|
7ee3c35785 | ||
|
|
01b725cf38 | ||
|
|
1ac6d90633 | ||
|
|
9704193237 | ||
|
|
69b150b391 | ||
|
|
7ffb3edda5 | ||
|
|
046eb68ce6 | ||
|
|
816d80a27c | ||
|
|
957037e5f4 | ||
|
|
9ca179a71f | ||
|
|
cb4d96f1ba | ||
|
|
7d57522775 | ||
|
|
902565f1b1 | ||
|
|
2f8de436af | ||
|
|
38bd035d99 | ||
|
|
88b6d1a3ca | ||
|
|
a74878e7bb | ||
|
|
2b6eba7c4d | ||
|
|
0de1b86ef5 | ||
|
|
ae0ab6471b | ||
|
|
c24cdaa8a4 | ||
|
|
df1442dd8a | ||
|
|
b17d5306db | ||
|
|
88d5294b8e | ||
|
|
8c3405d01f | ||
|
|
9f83ba700c | ||
|
|
ae147211ca | ||
|
|
01d305eba0 | ||
|
|
08c30574a9 | ||
|
|
39c4725a2f | ||
|
|
7002aeefaf | ||
|
|
f1201cc5bc | ||
|
|
0b99d4ac52 | ||
|
|
642cb701dd | ||
|
|
66d6aee115 | ||
|
|
38d8eb1108 | ||
|
|
f9941c5dff | ||
|
|
855a6f447a | ||
|
|
fa0a1f8553 | ||
|
|
0d1fee1636 | ||
|
|
ec8269332f | ||
|
|
00fe9400e6 | ||
|
|
8389b1e151 | ||
|
|
2289d10d6c | ||
|
|
b30af4f144 | ||
|
|
041322c53f | ||
|
|
548968bcfc | ||
|
|
6d405b70e7 | ||
|
|
1b4d4acc63 | ||
|
|
6f498089fb | ||
|
|
8e27cba0f6 | ||
|
|
ae48041a96 | ||
|
|
bad16a4cdc | ||
|
|
07baf769dd | ||
|
|
ab409643f6 | ||
|
|
c745a61601 | ||
|
|
c4e8e83d1e | ||
|
|
9133f3d792 | ||
|
|
b779d584a5 | ||
|
|
05bb61b3ab | ||
|
|
5d2885e662 | ||
|
|
ef645c1058 | ||
|
|
52a476fd0a | ||
|
|
7386c45f06 | ||
|
|
1a508ddedf | ||
|
|
a8675f254a | ||
|
|
0f70cd789e | ||
|
|
b8f594f42c | ||
|
|
7857527ce1 | ||
|
|
05c41a5469 | ||
|
|
f11f8afdc4 | ||
|
|
fa499aa46d | ||
|
|
e8a7cc026f | ||
|
|
433ef6a8a7 | ||
|
|
fc4b5a7bcd | ||
|
|
bd43134d58 | ||
|
|
557970ba4e | ||
|
|
d3418e0067 | ||
|
|
b8d1845d4e | ||
|
|
32e360409d | ||
|
|
c54a778af9 | ||
|
|
ce25698f87 | ||
|
|
4d95cc838a | ||
|
|
67fa0a231e | ||
|
|
3c2e8c81c4 | ||
|
|
484d46252b | ||
|
|
a2e3c67298 | ||
|
|
b2ea05d8dd | ||
|
|
1477f47962 | ||
|
|
273ce49c14 | ||
|
|
96b6790646 | ||
|
|
38227ec878 | ||
|
|
11da632972 | ||
|
|
3721b9fe22 | ||
|
|
ecb697e5fb | ||
|
|
c754772280 | ||
|
|
5f2b4ba0b3 | ||
|
|
0ab60becfc | ||
|
|
1dcfb7d6dc | ||
|
|
3c0b44b365 | ||
|
|
ec4aec50ef | ||
|
|
9c86cfe056 | ||
|
|
6bb2337276 | ||
|
|
4702ca255b | ||
|
|
ab47aa36cc | ||
|
|
4bb14c4a4e | ||
|
|
1dd75162f9 | ||
|
|
a2242e3c27 | ||
|
|
26ecf494a9 | ||
|
|
659af5d0b3 | ||
|
|
382a5ee7fc | ||
|
|
7364496844 | ||
|
|
d342316b87 | ||
|
|
b38141da88 | ||
|
|
98f80f7dae | ||
|
|
c134ec6bfe | ||
|
|
4e3f759243 | ||
|
|
a5f2e0157b | ||
|
|
fd21032e51 | ||
|
|
c4f5f9b7d3 | ||
|
|
6baf5a7068 | ||
|
|
d3460174de | ||
|
|
f9f19628ca | ||
|
|
04e48fea5d | ||
|
|
3e99e22fce | ||
|
|
0b244d5ff7 | ||
|
|
703f2ff316 | ||
|
|
34a2bbdbae | ||
|
|
cd8697c1a4 | ||
|
|
ed2dc2dd51 | ||
|
|
2dfdc1b993 | ||
|
|
6f2d7f1194 | ||
|
|
30009ef127 | ||
|
|
051a422331 | ||
|
|
fab6489e9c | ||
|
|
a448614e4a | ||
|
|
84ad874b2a | ||
|
|
19ba978291 | ||
|
|
e304469e55 | ||
|
|
7ce9e2592a | ||
|
|
681b812c7b | ||
|
|
ee1ab6785d | ||
|
|
57704b6622 | ||
|
|
7fe804e0fc | ||
|
|
a71529b641 | ||
|
|
4055173bdb | ||
|
|
3f8841a276 | ||
|
|
15c38851bc | ||
|
|
05030ae817 | ||
|
|
255fa11371 | ||
|
|
a074c0bf74 | ||
|
|
6ccd649fc4 | ||
|
|
21dbea07dd | ||
|
|
4c21d9d127 | ||
|
|
766002b079 | ||
|
|
2ce430ec41 | ||
|
|
cf2b151045 | ||
|
|
92216179df | ||
|
|
a2a95b8591 | ||
|
|
4a22dc111a | ||
|
|
5df7ecc245 | ||
|
|
d4ef520f26 | ||
|
|
e0f8737865 | ||
|
|
37b13a54a7 | ||
|
|
d9d28d31d0 | ||
|
|
66bf5da3f5 | ||
|
|
840c721afc | ||
|
|
859e705a48 | ||
|
|
7375070b21 | ||
|
|
3e2eda6a71 | ||
|
|
798d327dcb | ||
|
|
f5ca9139e6 | ||
|
|
f35e8cfa51 | ||
|
|
0f6953711b | ||
|
|
e2c5e6f9ac | ||
|
|
5ef5d6ddba | ||
|
|
5b19a5d798 | ||
|
|
bf804e8923 | ||
|
|
f47b8b5528 | ||
|
|
c353f6787a | ||
|
|
1b7d5c1a5c | ||
|
|
956456f157 | ||
|
|
49077bd798 | ||
|
|
019a0287b2 | ||
|
|
a71dac28c0 | ||
|
|
155886e000 | ||
|
|
ed4c303d2e | ||
|
|
9da0db5f74 | ||
|
|
562a973c84 | ||
|
|
967980ddf7 | ||
|
|
81d3675a87 | ||
|
|
7be786bbaf | ||
|
|
f41c9be1f7 | ||
|
|
e88062d028 | ||
|
|
edb2828643 | ||
|
|
58c189cd0e | ||
|
|
012994847e | ||
|
|
d87801be5f | ||
|
|
6d78c0c104 | ||
|
|
3a83519122 | ||
|
|
a9f940ebed | ||
|
|
db73d9ef81 | ||
|
|
e2bd21c717 | ||
|
|
871215b7b5 | ||
|
|
a6153ef46f | ||
|
|
35475a6103 | ||
|
|
bf77ffc1a5 | ||
|
|
7a64a9a534 | ||
|
|
68e057b99c | ||
|
|
7338afff96 | ||
|
|
97ae7813aa | ||
|
|
c0f3667ac1 | ||
|
|
d5c5502abe | ||
|
|
194e8ffbd9 | ||
|
|
ef39458abd | ||
|
|
6a6907fb0a | ||
|
|
8bb7c4a4a6 | ||
|
|
91700e7ed0 | ||
|
|
661c2e6fc3 | ||
|
|
3d053a6149 | ||
|
|
34179e4f62 | ||
|
|
a23e5d9f50 | ||
|
|
1cbcbf70d9 | ||
|
|
0a7371aaec | ||
|
|
da13e2d861 | ||
|
|
5b18fa8c25 | ||
|
|
347231850a | ||
|
|
b1f9bdc26c | ||
|
|
03c9b95041 | ||
|
|
6e7e1d783b | ||
|
|
cfb9b0a029 | ||
|
|
b7ea6e5af6 | ||
|
|
44104df712 | ||
|
|
5de20d1c58 | ||
|
|
d156303740 | ||
|
|
6e36eb3554 | ||
|
|
f7f3891817 | ||
|
|
1ec28856ff | ||
|
|
9658e0b74d | ||
|
|
86cd8d4151 | ||
|
|
2418eb206e | ||
|
|
08fa715d26 | ||
|
|
52426de283 | ||
|
|
64b3da14c4 | ||
|
|
bf7341c52c | ||
|
|
e3490564a2 | ||
|
|
6218f0ed16 | ||
|
|
54e8e1ea98 | ||
|
|
4cd50fb3d5 | ||
|
|
590286a6c2 | ||
|
|
6c96650dcc | ||
|
|
7c83a74fd8 | ||
|
|
7b3f60cb3b | ||
|
|
bfc845088a | ||
|
|
e67d93a592 | ||
|
|
b518b980ae | ||
|
|
744bc1403a | ||
|
|
e11113073f | ||
|
|
3615818812 | ||
|
|
0cdef12caf | ||
|
|
dda8c6c053 | ||
|
|
8c3327f553 | ||
|
|
c037a14e1f | ||
|
|
bd661360ee | ||
|
|
317fabbc87 | ||
|
|
87161446c5 | ||
|
|
7db8b2e84b | ||
|
|
5f3fa2b317 | ||
|
|
64c15730a6 | ||
|
|
e5ab46b8ad | ||
|
|
3060d8cc2b | ||
|
|
f5d6ba462e | ||
|
|
81fdeece96 | ||
|
|
8f60439ef3 | ||
|
|
c5420ead90 | ||
|
|
99d372d03c | ||
|
|
1219a9822f | ||
|
|
d40c182af9 | ||
|
|
785e986a72 | ||
|
|
e8b80f455b | ||
|
|
2d545f1bf1 | ||
|
|
d4713dc7f4 | ||
|
|
cd96c13670 | ||
|
|
808607faf0 | ||
|
|
bc403f5c23 | ||
|
|
bfc67e1953 | ||
|
|
a186c16c34 | ||
|
|
f9be9d4c4b | ||
|
|
79fc3ce33f | ||
|
|
055ff8749e | ||
|
|
d36410e851 | ||
|
|
0fed5dca7f | ||
|
|
dd34ffc5b2 | ||
|
|
2cd5ac85f9 | ||
|
|
571f949f50 | ||
|
|
81e32957b2 | ||
|
|
4639016ffb | ||
|
|
dabc2b49dc | ||
|
|
2dec389c8c | ||
|
|
bfe5b5960e | ||
|
|
e352967a17 | ||
|
|
930917f285 | ||
|
|
b9747f7fbe | ||
|
|
5ac9dcec37 | ||
|
|
8328b43783 | ||
|
|
131fcfe144 | ||
|
|
709cb967b5 | ||
|
|
3370b83087 | ||
|
|
5c44bd500c | ||
|
|
ab8a345944 | ||
|
|
337dd56c87 | ||
|
|
de78ae30db | ||
|
|
3f64185389 | ||
|
|
5077cbbc9a | ||
|
|
4d522ba5f5 | ||
|
|
4f1180b3f5 | ||
|
|
577a3f2551 | ||
|
|
8623ab053e | ||
|
|
6d0f99eaf1 | ||
|
|
f9490d2652 | ||
|
|
9d321e4a31 | ||
|
|
b8c2625b80 | ||
|
|
8e42f328dc | ||
|
|
0c6b4efba5 | ||
|
|
0821ed4dae | ||
|
|
8bc38e25f9 | ||
|
|
7a7a4e6da2 | ||
|
|
f7d703ed1e | ||
|
|
443d7fd082 | ||
|
|
b6114625c1 | ||
|
|
4a3eb78f12 | ||
|
|
fa117a671a | ||
|
|
6448fb975a | ||
|
|
1c685ce0bb | ||
|
|
1662fa8d60 | ||
|
|
2787b1735b | ||
|
|
e58390e30b | ||
|
|
41a10a61bf | ||
|
|
fdcc7d5e7c | ||
|
|
50b39e462f | ||
|
|
4bc781f0da | ||
|
|
aca4c00083 | ||
|
|
77b820ad4d | ||
|
|
328f604236 | ||
|
|
2f77bf3d10 | ||
|
|
23eb55aa06 | ||
|
|
32b7356bc0 | ||
|
|
6091641ed9 | ||
|
|
e368213d18 | ||
|
|
58f9bb6fca | ||
|
|
79798199b8 | ||
|
|
c8d20f8479 | ||
|
|
c0498a60b4 | ||
|
|
faa01ddfc2 | ||
|
|
a2f628e267 | ||
|
|
66007b9121 | ||
|
|
9e0056b603 | ||
|
|
b15cc3a6af | ||
|
|
5393f869e7 | ||
|
|
f8088402e8 | ||
|
|
979ff7d2b8 | ||
|
|
bdbde58f42 | ||
|
|
1e26e0bd13 | ||
|
|
be3c00d17e | ||
|
|
d542bb67de | ||
|
|
a79cdfaa1c | ||
|
|
42db715421 | ||
|
|
2228d5a35d | ||
|
|
c434d0eafd | ||
|
|
414a3a971f | ||
|
|
b8a22a2561 | ||
|
|
bc1dc2f84f | ||
|
|
904c4f27aa | ||
|
|
b19e5435ef | ||
|
|
78fbcd829a | ||
|
|
a255844a92 | ||
|
|
c7a548c575 | ||
|
|
90a4bab114 | ||
|
|
a1633d146a | ||
|
|
87bf4b03df | ||
|
|
7731aea0d6 | ||
|
|
e66350111e | ||
|
|
7e5df68626 | ||
|
|
8e3e7de4ee | ||
|
|
0ee535fbd5 | ||
|
|
1b471d266c | ||
|
|
19a4f6ea1f | ||
|
|
7cc8d71224 | ||
|
|
b843475a0a | ||
|
|
027c0fa771 | ||
|
|
c08e76f110 | ||
|
|
b837fe7d15 | ||
|
|
d4d1bed408 | ||
|
|
8870295d54 | ||
|
|
79cd6e4ae2 | ||
|
|
8e5ea29c40 | ||
|
|
287b51a0bf | ||
|
|
28cbf952fa | ||
|
|
598c35a3aa | ||
|
|
b8b67a64ab | ||
|
|
7f6020ef34 | ||
|
|
aeed453e15 | ||
|
|
71dddcca4c | ||
|
|
46cdf79e20 | ||
|
|
d7af96823b | ||
|
|
5a228bd6b3 | ||
|
|
fcd95655ce | ||
|
|
7f5b57f8d2 | ||
|
|
f596877716 | ||
|
|
1802c8fd90 | ||
|
|
e05856facc | ||
|
|
45cfc3abb0 | ||
|
|
2d39579861 | ||
|
|
431f4bb4d5 | ||
|
|
7bc9d38176 | ||
|
|
7536447a8f | ||
|
|
059b796061 | ||
|
|
a89942484f | ||
|
|
c28be3fd65 | ||
|
|
5f1116795c | ||
|
|
4f54d2f6b2 | ||
|
|
6b7bedbe2d | ||
|
|
20ff225c31 | ||
|
|
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 |
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
|
||||
@@ -60,4 +60,6 @@ body:
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
|
||||
required: true
|
||||
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
|
||||
required: true
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Compatibility issue
|
||||
description: A compatibility issue with another plugin/mod or a specific version of a platform marked as supported
|
||||
labels: ["Type: Compatibility issue"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 4.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Software causing compatibility issue
|
||||
description: plugin/mod/platform version causing the issue
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Anything else you want to provide that will help in resolving the issue faster.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: The compatibility wiki page does not mention this incompatibility
|
||||
required: true
|
||||
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
|
||||
required: true
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Support
|
||||
url: https://discord.gg/YPqXt63YQj
|
||||
about: Fresh new community discord providing support with all plugins, including TAB
|
||||
@@ -3,21 +3,19 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0 #https://github.com/actions/checkout/releases
|
||||
- uses: actions/checkout@v4 #https://github.com/actions/checkout/releases
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4 #https://github.com/actions/setup-java/releases
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 11
|
||||
java-version: 21
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
@@ -40,5 +38,5 @@ jobs:
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
|
||||
with:
|
||||
name: Jar
|
||||
name: Click here to download
|
||||
path: jar/build/libs/TAB-**.jar
|
||||
@@ -33,7 +33,6 @@ hs_err_pid*
|
||||
.idea/
|
||||
|
||||
# other
|
||||
.gitattributes
|
||||
*.iml
|
||||
run/
|
||||
|
||||
|
||||
+1
-2
@@ -6,5 +6,4 @@ is in quality of the report (typically providing correct steps to reproduce that
|
||||
* Test **experimental builds**, which can be found in [GitHub Actions](https://github.com/NEZNAMY/TAB/actions) for problems of any kind including bugs.
|
||||
This way you will help make the actual releases more stable / help get stable releases sooner.
|
||||
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
|
||||
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples of usage of functions.
|
||||
* [Buy a copy](https://builtbybit.com/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
|
||||
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples.
|
||||
@@ -1,86 +1,58 @@
|
||||
# About
|
||||
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
|
||||
similar plugins in terms of features, performance, compatibility and support.
|
||||
similar plugins in terms of features, performance and compatibility.
|
||||
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
|
||||
|
||||
# Distribution
|
||||
As of TAB 3.0.0, released with MC 1.18 (November 30th, 2021), there is no free / premium
|
||||
version anymore. Only one version of the plugin exists now, which contains all implemented features
|
||||
and is available to everyone for free. However, customer support is only provided to
|
||||
customers who have purchased the plugin (see [Customer support](#customer-support)).
|
||||
# Download
|
||||
### Releases
|
||||
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
|
||||
[SpigotMC](https://www.spigotmc.org/resources/57806/), [Modrinth](https://modrinth.com/plugin/tab-was-taken) or [BuiltByBit](https://builtbybit.com/resources/20631).
|
||||
|
||||
### Dev builds
|
||||
Plugin's development is continuous and contains lots of small changes/fixes not worthy of being called a full
|
||||
update, therefore development builds are being created and eventually a full update is released when enough changes are made.
|
||||
If you want to benefit from a recent change in the code before official release, you can download development builds
|
||||
from [GitHub actions](https://github.com/NEZNAMY/TAB/actions) (you must be logged into GitHub to be able to download it).
|
||||
|
||||
# Compiling
|
||||
Compilation requires JDK 8 and up.
|
||||
Compilation requires JDK 21 and up.
|
||||
To compile the plugin, run `./gradlew build` from the terminal.
|
||||
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
|
||||
The universal jar contains all modules for all supported platforms.
|
||||
|
||||
# Customer support
|
||||
Unlike with other projects where requirements and level of support provided are unclear,
|
||||
we provide a clear answer to what kind of service you will or will not receive. Some don't
|
||||
even clearly state if they are free or paid. While they are listed as paid on spigot, they have
|
||||
a free download link in description, which can be confusing.
|
||||
|
||||
Free plugins typically do have a support discord, however you will rarely receive
|
||||
official support from the developers, especially for large projects with tons of users, where
|
||||
helping everyone in the world (theoretically) would consume too much time, leaving authors
|
||||
no time left to actually work on the project.
|
||||
In the end, they are free, made by people in their free time, and they are allowed to choose
|
||||
what they do or don't want to do in their free time. They often leave support to other people,
|
||||
possibly staff members, who are, however, in the end just more volunteers who may not even know
|
||||
the answer.
|
||||
|
||||
With paid plugins you are more likely to receive support, but unless explicitly said, nothing is
|
||||
guaranteed. There are lots of paid plugins where authors don't provide any support or redirect
|
||||
it to helpers, where again they are unpaid volunteers who might not even know the answer.
|
||||
|
||||
In TAB, everything is clearly defined. The plugin is free to download for everyone.
|
||||
Customer support, however, is only provided to customers who have formally purchased a copy
|
||||
of the plugin on any of the websites the plugin is being sold on:
|
||||
* [BuiltByBit](https://builtbybit.com/resources/14009/) (accepts any payment method)
|
||||
* [Polymart](https://polymart.org/resource/484) (only PayPal)
|
||||
|
||||
After purchasing, you can request assistance via either private messages on the website where you
|
||||
have purchased it, or using [TAB's Discord server](https://discord.gg/EaSvdk6) (which is highly
|
||||
preferred, since the communication will be much faster thanks to notifications and more).
|
||||
* We use a ticket system, which allows us to effectively track any amount of unresolved problems.
|
||||
No more questions getting lost in the sea of messages from other people asking. Tickets are only closed
|
||||
after user confirmed all of their questions were answered and then receive a transcript of the ticket
|
||||
from the bot after ticket is closed into private messages (unless user is blocking them).
|
||||
* We do not rely on the community helping, which often makes things even worse. We know people have
|
||||
better things to do than helping random people with a random plugin, especially when most people only
|
||||
have a very basic knowledge of the plugin, not being able to answer more complicated problems.
|
||||
* We have notifications enabled and reply as soon as possible. We do not have the server muted unlike
|
||||
most mods on most servers who only check it when they are really bored, give a wiki link and
|
||||
disappear again.
|
||||
* As the developer, I do not rely on helpers providing support instead of me. They are handy as
|
||||
I am obviously not available 24/7, but I do not intentionally leave anything to them. I reply
|
||||
whenever I am available without any intentional delays.
|
||||
|
||||
The wiki contains everything about the plugin in one way or another, so support should not
|
||||
even be needed. It also contains a few examples, but obviously cannot contain a pre-made config
|
||||
for every possible idea anyone can come up with.
|
||||
Support is provided to people who decided paying for support is more time efficient than reading the wiki,
|
||||
especially for serious servers, where price of the plugin is nothing compared to other server costs.
|
||||
|
||||
# Other links
|
||||
Download the plugin for free:
|
||||
* [GitHub releases](https://github.com/NEZNAMY/TAB/releases)
|
||||
* [BuiltByBit](https://builtbybit.com/resources/20631/)
|
||||
* [Modrinth](https://modrinth.com/plugin/tab-was-taken)
|
||||
|
||||
[Wiki](https://github.com/NEZNAMY/TAB/wiki)
|
||||
[Donate](https://paypal.me/neznamy1) if you are feeling extra generous
|
||||
# Documentation
|
||||
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
|
||||
of all features, as well as information regarding compatibility or limitations of each feature.
|
||||
|
||||
# Issues
|
||||
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
|
||||
In other words, they **are not available for**:
|
||||
* **Support** - support is provided on discord for buyers, where roles are given to verified users
|
||||
allowing us to know who bought it and who didn't. It also serves as a much faster way of communication.
|
||||
* **Feature requests** - closed indefinitely.
|
||||
|
||||
**If you're a customer, please use discord for reporting bugs, especially if you aren't good at making bug reports.
|
||||
You will be provided with a much faster and higher quality service.**
|
||||
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is an issue tracker for all the kind of problems covered in issue templates.
|
||||
Please refrain from attempting to use it to receive help with your server, such issues will be closed immediately.
|
||||
When opening an issue, provide as much information as possible. The difference between a bug getting fixed in 1 week vs 1 hour
|
||||
is in quality of the report. Bug reports where steps to reproduce are "idk" have no value and will not help to get the bug fixed at all.
|
||||
|
||||
# Contributing
|
||||
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
|
||||
|
||||
# Provided services
|
||||
The plugin is being used by a large amount of servers, which also means the demand for my attention is high. Unfortunately,
|
||||
I am no longer able to keep up with the demand since I don't want to dedicate my whole existence to a free plugin.
|
||||
In order to provide the best quality of all provided services, the amount of services provided may vary over time.
|
||||
Here is the full list of potential services and their status:
|
||||
|
||||
| Service type | Availability |
|
||||
|-------------------------------|--------------|
|
||||
| Updates for new MC versions | ✔ |
|
||||
| Bug fixes | ✔ |
|
||||
| Wiki updates<sup>1</sup> | ✔ |
|
||||
| Feature requests | ❌ |
|
||||
| Customer support<sup>2</sup> | ✔ |
|
||||
| Free user support<sup>3</sup> | ❌* |
|
||||
| Optimization<sup>4</sup> | ✔ |
|
||||
|
||||
<sup>1</sup> - Adding new examples, guides and other kind of useful information people ask about.
|
||||
<sup>2</sup> - Unrestricted assistance with the plugin for those, who bought a copy of the plugin when it was purchasable.
|
||||
<sup>3</sup> - Unlimited access to my free time for everyone in the world.
|
||||
<sup>4</sup> - Improving plugin's performance while not losing out on any features. This was done
|
||||
proactively a lot in the past, so there isn't much to do anymore, only if someone actually finds and reports something.
|
||||
|
||||
*Instead, you can use [this fresh new community discord](https://discord.gg/YPqXt63YQj) made by a nice person who will provide this service.
|
||||
+1
-24
@@ -1,28 +1,5 @@
|
||||
plugins {
|
||||
id("net.kyori.blossom") version "1.2.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnlyApi(libs.netty)
|
||||
api(libs.jsonSimple) {
|
||||
exclude("junit", "junit")
|
||||
}
|
||||
compileOnlyApi(libs.snakeyaml)
|
||||
api(libs.yamlAssist)
|
||||
compileOnlyApi(libs.adventureMiniMessage)
|
||||
compileOnlyApi(libs.adventureApi)
|
||||
compileOnlyApi(libs.adventureLegacy)
|
||||
compileOnlyApi(libs.via)
|
||||
}
|
||||
|
||||
blossom {
|
||||
replaceToken("@name@", rootProject.name)
|
||||
replaceToken("@id@", rootProject.ext.get("id")!!.toString())
|
||||
replaceToken("@version@", project.version)
|
||||
replaceToken("@description@", project.description)
|
||||
replaceToken("@website@", rootProject.ext.get("website")!!.toString())
|
||||
replaceToken("@author@", rootProject.ext.get("author")!!.toString())
|
||||
replaceTokenIn("src/main/java/me/neznamy/tab/api/TabConstants.java")
|
||||
compileOnlyApi("org.jetbrains:annotations:24.1.0")
|
||||
}
|
||||
|
||||
tasks.javadoc {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
/**
|
||||
* Interface representing an armor stand attached to player
|
||||
* for displaying text in game.
|
||||
*/
|
||||
public interface ArmorStand {
|
||||
|
||||
/**
|
||||
* Returns true if offset is static, false if not
|
||||
*
|
||||
* @return true if static, false if dynamic
|
||||
*/
|
||||
boolean isStaticOffset();
|
||||
|
||||
/**
|
||||
* Changes offset of the armor stand
|
||||
*
|
||||
* @param offset
|
||||
* new offset
|
||||
*/
|
||||
void setOffset(double offset);
|
||||
|
||||
/**
|
||||
* Returns offset of the armor stand
|
||||
*
|
||||
* @return offset from base
|
||||
*/
|
||||
double getOffset();
|
||||
|
||||
/**
|
||||
* Returns property for armor stand's name
|
||||
*
|
||||
* @return property for armor stand's name
|
||||
*/
|
||||
Property getProperty();
|
||||
|
||||
/**
|
||||
* Teleports armor stand to owner's current location for everyone in range
|
||||
*/
|
||||
void teleport();
|
||||
|
||||
/**
|
||||
* Teleports armor stand to owner's current location for specified player
|
||||
*
|
||||
* @param viewer
|
||||
* player to send packet to
|
||||
*/
|
||||
void teleport(TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Updates armor stand's name if needed
|
||||
*/
|
||||
void refresh();
|
||||
|
||||
/**
|
||||
* Updates visibility if needed
|
||||
*
|
||||
* @param force
|
||||
* if refresh should be forced
|
||||
*/
|
||||
void updateVisibility(boolean force);
|
||||
|
||||
/**
|
||||
* Returns entity ID of this armor stand
|
||||
*
|
||||
* @return entity ID of this armor stand
|
||||
*/
|
||||
int getEntityId();
|
||||
|
||||
/**
|
||||
* Spawns armor stand for specified player
|
||||
*
|
||||
* @param viewer
|
||||
* player to spawn for
|
||||
*/
|
||||
void spawn(TabPlayer viewer);
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Feature manager allows registration and work with features.
|
||||
*/
|
||||
public interface FeatureManager {
|
||||
|
||||
/**
|
||||
* Registers a feature, which will start receiving events
|
||||
*
|
||||
* @param name
|
||||
* name of feature
|
||||
* @param feature
|
||||
* the handler
|
||||
*/
|
||||
void registerFeature(String name, TabFeature feature);
|
||||
|
||||
/**
|
||||
* Unregisters feature making it no longer receive events. This does not run unload method nor cancel
|
||||
* tasks created by the feature
|
||||
*
|
||||
* @param name
|
||||
* feature name defined in registerFeature
|
||||
*/
|
||||
void unregisterFeature(String name);
|
||||
|
||||
/**
|
||||
* Returns whether a feature with said name is registered or not
|
||||
*
|
||||
* @param name
|
||||
* name of feature defined in registerFeature method
|
||||
* @return true if feature exists, false if not
|
||||
*/
|
||||
boolean isFeatureEnabled(String name);
|
||||
|
||||
/**
|
||||
* Returns feature handler by its name
|
||||
*
|
||||
* @param name
|
||||
* name of feature defined in registerFeature method
|
||||
* @return the feature or null if feature does not exist
|
||||
*/
|
||||
TabFeature getFeature(String name);
|
||||
|
||||
/**
|
||||
* Calls onQuit(TabPlayer) to all enabled features
|
||||
*
|
||||
* @param disconnectedPlayer
|
||||
* player who disconnected
|
||||
*/
|
||||
void onQuit(TabPlayer disconnectedPlayer);
|
||||
|
||||
/**
|
||||
* Calls onJoin(TabPlayer) to all enabled features
|
||||
*
|
||||
* @param connectedPlayer
|
||||
* player who connected
|
||||
*/
|
||||
void onJoin(TabPlayer connectedPlayer);
|
||||
|
||||
/**
|
||||
* Calls onServerChange(TabPlayer, String, String) to all enabled features
|
||||
*
|
||||
* @param playerUUID
|
||||
* player who switched server
|
||||
* @param to
|
||||
* name of the new server
|
||||
*/
|
||||
void onServerChange(UUID playerUUID, String to);
|
||||
|
||||
/**
|
||||
* Calls onWorldChange(TabPlayer, String, String) to all enabled features
|
||||
*
|
||||
* @param playerUUID
|
||||
* player who switched world
|
||||
* @param to
|
||||
* name of the new world
|
||||
*/
|
||||
void onWorldChange(UUID playerUUID, String to);
|
||||
|
||||
/**
|
||||
* Calls onCommand(TabPlayer, String) to all enabled features
|
||||
*
|
||||
* @param sender
|
||||
* command sender
|
||||
* @param command
|
||||
* command line including /
|
||||
* @return {@code true} if some feature marked the command for cancel, {@code false} if not
|
||||
*/
|
||||
boolean onCommand(TabPlayer sender, String command);
|
||||
|
||||
/**
|
||||
* Internal function that marks objective packet deserialization
|
||||
* when at least 1 feature uses it.
|
||||
* <p>
|
||||
* Internal use only.
|
||||
*/
|
||||
void markObjective();
|
||||
|
||||
/**
|
||||
* Internal function that marks display objective packet deserialization
|
||||
* when at least 1 feature uses it.
|
||||
* <p>
|
||||
* Internal use only.
|
||||
*/
|
||||
void markDisplayObjective();
|
||||
|
||||
/**
|
||||
* Returns array of all currently enabled features
|
||||
*
|
||||
* @return array of all currently enabled features
|
||||
*/
|
||||
TabFeature[] getValues();
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
/**
|
||||
* An interface representing a property assigned to a player,
|
||||
* which may contain placeholders and can be changed using the API.
|
||||
*/
|
||||
public interface Property {
|
||||
|
||||
/**
|
||||
* Returns original raw value ignoring API calls
|
||||
*
|
||||
* @return original raw value
|
||||
*/
|
||||
String getOriginalRawValue();
|
||||
|
||||
/**
|
||||
* Temporarily overrides current raw value with an API call
|
||||
*
|
||||
* @param temporaryValue
|
||||
* temporary value to be assigned
|
||||
*/
|
||||
void setTemporaryValue(String temporaryValue);
|
||||
|
||||
/**
|
||||
* Returns current temporary value
|
||||
*
|
||||
* @return temporary value or null if not set
|
||||
*/
|
||||
String getTemporaryValue();
|
||||
|
||||
/**
|
||||
* Returns temporary value (via API) if present, raw value otherwise
|
||||
*
|
||||
* @return current raw value
|
||||
*/
|
||||
String getCurrentRawValue();
|
||||
|
||||
/**
|
||||
* Returns last known value
|
||||
*
|
||||
* @return last known value
|
||||
*/
|
||||
String get();
|
||||
|
||||
/**
|
||||
* Replaces all placeholders in current raw value, colorizes it, removes remove-strings and returns whether value changed or not
|
||||
*
|
||||
* @return if updating changed value or not
|
||||
*/
|
||||
boolean update();
|
||||
|
||||
/**
|
||||
* Replaces all placeholders in current raw value, colorizes it, removes remove-strings and returns it.
|
||||
* Equal to calling update() and then get().
|
||||
*
|
||||
* @return updated value
|
||||
*/
|
||||
String updateAndGet();
|
||||
|
||||
/**
|
||||
* Returns value for defined viewer by applying relational placeholders to last known value
|
||||
*
|
||||
* @param viewer
|
||||
* the viewer
|
||||
* @return format for the viewer
|
||||
*/
|
||||
String getFormat(TabPlayer viewer);
|
||||
}
|
||||
@@ -2,13 +2,19 @@ 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.config.ConfigurationFile;
|
||||
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.task.ThreadManager;
|
||||
import me.neznamy.tab.api.team.TeamManager;
|
||||
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
|
||||
@@ -16,14 +22,7 @@ import me.neznamy.tab.api.team.TeamManager;
|
||||
public abstract class TabAPI {
|
||||
|
||||
/** Instance of the API */
|
||||
private static TabAPI instance;
|
||||
|
||||
/**
|
||||
* Constructs new instance and sets it as new API instance
|
||||
*/
|
||||
public TabAPI() {
|
||||
instance = this;
|
||||
}
|
||||
@Setter private static TabAPI instance;
|
||||
|
||||
/**
|
||||
* Returns API instance. If instance was not set by the plugin, throws
|
||||
@@ -35,10 +34,10 @@ public abstract class TabAPI {
|
||||
* @throws IllegalStateException
|
||||
* If instance is {@code null}
|
||||
*/
|
||||
public static TabAPI getInstance() {
|
||||
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 softdepend of your plugin.\n" +
|
||||
"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.");
|
||||
@@ -52,7 +51,7 @@ public abstract class TabAPI {
|
||||
* Player UUID
|
||||
* @return player object from given UUID
|
||||
*/
|
||||
public abstract TabPlayer getPlayer(UUID id);
|
||||
public abstract @Nullable TabPlayer getPlayer(@NonNull UUID id);
|
||||
|
||||
/**
|
||||
* Returns player object from given name
|
||||
@@ -61,157 +60,76 @@ public abstract class TabAPI {
|
||||
* Player name
|
||||
* @return player object from given name
|
||||
*/
|
||||
public abstract TabPlayer getPlayer(String 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 TabPlayer[] getOnlinePlayers();
|
||||
public abstract @NotNull TabPlayer[] getOnlinePlayers();
|
||||
|
||||
/**
|
||||
* Return BossBar manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return BossBar manager
|
||||
*/
|
||||
public abstract BossBarManager getBossBarManager();
|
||||
public abstract @Nullable BossBarManager getBossBarManager();
|
||||
|
||||
/**
|
||||
* Returns scoreboard manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return scoreboard manager
|
||||
*/
|
||||
public abstract ScoreboardManager getScoreboardManager();
|
||||
public abstract @Nullable ScoreboardManager getScoreboardManager();
|
||||
|
||||
/**
|
||||
* Returns team manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return team manager
|
||||
*/
|
||||
public abstract TeamManager getTeamManager();
|
||||
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 HeaderFooterManager getHeaderFooterManager();
|
||||
public abstract @Nullable HeaderFooterManager getHeaderFooterManager();
|
||||
|
||||
/**
|
||||
* Returns PlaceholderManager instance
|
||||
*
|
||||
* @return PlaceholderManager instance
|
||||
*/
|
||||
public abstract PlaceholderManager getPlaceholderManager();
|
||||
public abstract @NotNull PlaceholderManager getPlaceholderManager();
|
||||
|
||||
/**
|
||||
* Returns feature manager instance
|
||||
* Returns TabList name format manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return feature manager instance
|
||||
* @return TabList name format manager
|
||||
*/
|
||||
public abstract FeatureManager getFeatureManager();
|
||||
public abstract @Nullable TabListFormatManager getTabListFormatManager();
|
||||
|
||||
/**
|
||||
* Returns Tablist name format manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
* Returns Layout manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return Tablist name format manager
|
||||
* @return Layout manager
|
||||
*/
|
||||
public abstract TablistFormatManager getTablistFormatManager();
|
||||
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 EventBus getEventBus();
|
||||
|
||||
/**
|
||||
* Returns server version. On proxy installation returns PROXY.
|
||||
*
|
||||
* @return server version
|
||||
*/
|
||||
public abstract ProtocolVersion getServerVersion();
|
||||
|
||||
/**
|
||||
* Prints message into console
|
||||
*
|
||||
* @param message
|
||||
* message to print
|
||||
* @param translateColors
|
||||
* {@code true} if colors should be translated, {@code false} if not
|
||||
*/
|
||||
public abstract void sendConsoleMessage(String message, boolean translateColors);
|
||||
|
||||
/**
|
||||
* Returns TAB's Thread manager, which allows task submitting
|
||||
*
|
||||
* @return ThreadManager instance
|
||||
*/
|
||||
public abstract ThreadManager getThreadManager();
|
||||
|
||||
/**
|
||||
* Returns TAB's cache file used to store player toggle data
|
||||
*
|
||||
* @return TAB's player cache file
|
||||
*/
|
||||
public abstract ConfigurationFile getPlayerCache();
|
||||
|
||||
/**
|
||||
* Returns TAB's config.yml file
|
||||
*
|
||||
* @return config.yml file
|
||||
*/
|
||||
public abstract ConfigurationFile getConfig();
|
||||
|
||||
/**
|
||||
* Returns TAB's group configuration
|
||||
*
|
||||
* @return TAB's group configuration
|
||||
*/
|
||||
public abstract PropertyConfiguration getGroups();
|
||||
|
||||
/**
|
||||
* Returns TAB's user configuration
|
||||
*
|
||||
* @return TAB's user configuration
|
||||
*/
|
||||
public abstract PropertyConfiguration getUsers();
|
||||
|
||||
/**
|
||||
* Sends a debug message into console if the option
|
||||
* is enabled in config.
|
||||
*
|
||||
* @param message
|
||||
* Message to send
|
||||
*/
|
||||
public abstract void debug(String message);
|
||||
|
||||
/**
|
||||
* Logs an error into errors.log file
|
||||
*
|
||||
* @param message
|
||||
* Error message
|
||||
* @param t
|
||||
* Thrown error
|
||||
*/
|
||||
public abstract void logError(String message, Throwable t);
|
||||
|
||||
/**
|
||||
* Sets name of file with syntax error, which prevented
|
||||
* the plugin from enabling. Internal use only.
|
||||
*
|
||||
* @param file
|
||||
* Name of file with syntax error
|
||||
*/
|
||||
public abstract void setBrokenFile(String file);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the plugin is currently disabled due to an error,
|
||||
* either a yaml syntax error or an internal startup error. Returns {@code false}
|
||||
* if the plugin is properly running.
|
||||
*
|
||||
* @return {@code true} if disabled, {@code false} if running correctly
|
||||
*/
|
||||
public abstract boolean isPluginDisabled();
|
||||
public abstract @Nullable EventBus getEventBus();
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
|
||||
|
||||
/**
|
||||
* Abstract class representing a core feature of the plugin.
|
||||
* <p>
|
||||
* It receives all kinds of events and can react to them.
|
||||
*/
|
||||
public abstract class TabFeature {
|
||||
|
||||
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
|
||||
protected String[] disabledServers = new String[0];
|
||||
|
||||
/** Flag tracking whether disabled server list is a whitelist or blacklist */
|
||||
private boolean serverWhitelistMode = false;
|
||||
|
||||
/** Worlds where the feature is disabled (or enabled if using whitelist mode) */
|
||||
protected String[] disabledWorlds = new String[0];
|
||||
|
||||
/** Flag tracking whether disabled world list is a whitelist or blacklist */
|
||||
private boolean worldWhitelistMode = false;
|
||||
|
||||
/** Players located in currently disabled worlds / servers */
|
||||
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
|
||||
/**
|
||||
* List of methods overridden (implemented) by each feature.
|
||||
* If a method is not overridden, it is not called at all.
|
||||
* This avoids a massive spam for every single method
|
||||
* in every single feature in /tab cpu output
|
||||
*/
|
||||
private final List<String> methodOverrides = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads method overrides
|
||||
*/
|
||||
protected TabFeature() {
|
||||
try {
|
||||
if (getClass().getMethod("onCommand", TabPlayer.class, String.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onCommand");
|
||||
if (getClass().getMethod("load").getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("load");
|
||||
if (getClass().getMethod("unload").getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("unload");
|
||||
if (getClass().getMethod("onJoin", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onJoin");
|
||||
if (getClass().getMethod("onQuit", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onQuit");
|
||||
if (getClass().getMethod("onWorldChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onWorldChange");
|
||||
if (getClass().getMethod("onServerChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onServerChange");
|
||||
if (getClass().getMethod("onDisplayObjective", TabPlayer.class, PacketPlayOutScoreboardDisplayObjective.class).getDeclaringClass() != TabFeature.class) {
|
||||
methodOverrides.add("onDisplayObjective");
|
||||
TabAPI.getInstance().getFeatureManager().markDisplayObjective();
|
||||
}
|
||||
if (getClass().getMethod("onLoginPacket", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onLoginPacket");
|
||||
if (getClass().getMethod("onObjective", TabPlayer.class, PacketPlayOutScoreboardObjective.class).getDeclaringClass() != TabFeature.class) {
|
||||
methodOverrides.add("onObjective");
|
||||
TabAPI.getInstance().getFeatureManager().markObjective();
|
||||
}
|
||||
if (getClass().getMethod("onPlayerInfo", TabPlayer.class, PacketPlayOutPlayerInfo.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onPlayerInfo");
|
||||
if (getClass().getMethod("onPacketReceive", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onPacketReceive");
|
||||
if (getClass().getMethod("onPacketSend", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onPacketSend");
|
||||
if (getClass().getMethod("refresh", TabPlayer.class, boolean.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("refresh");
|
||||
if (getClass().getMethod("onVanishStatusChange", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onVanishStatusChange");
|
||||
} catch (NoSuchMethodException e) {
|
||||
//this will never happen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads method overrides.
|
||||
* Also loads lists of disabled worlds and servers of this feature with config
|
||||
* section path specified with {@code configSection} parameter.
|
||||
*
|
||||
* @param configSection
|
||||
* Configuration section of the feature to load disabled
|
||||
* servers / worlds from
|
||||
*/
|
||||
protected TabFeature(@NonNull String configSection) {
|
||||
this();
|
||||
List<String> disabledServers = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-servers");
|
||||
List<String> disabledWorlds = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-worlds");
|
||||
if (disabledServers != null) {
|
||||
this.disabledServers = disabledServers.toArray(new String[0]);
|
||||
serverWhitelistMode = disabledServers.contains("WHITELIST");
|
||||
}
|
||||
if (disabledWorlds != null) {
|
||||
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
|
||||
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all online players and sends packets
|
||||
*/
|
||||
public void load() {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads all players and sends clear packets
|
||||
*/
|
||||
public void unload() {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes command from player. This is typically a toggle command.
|
||||
*
|
||||
* @param sender
|
||||
* command sender
|
||||
* @param message
|
||||
* command line
|
||||
* @return {@code true} if event should be cancelled, {@code true} if not
|
||||
*/
|
||||
public boolean onCommand(TabPlayer sender, String message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes join event
|
||||
*
|
||||
* @param connectedPlayer
|
||||
* player who connected
|
||||
*/
|
||||
public void onJoin(TabPlayer connectedPlayer) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes quit event
|
||||
*
|
||||
* @param disconnectedPlayer
|
||||
* player who disconnected
|
||||
*/
|
||||
public void onQuit(TabPlayer disconnectedPlayer) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes world switch
|
||||
*
|
||||
* @param changed
|
||||
* player who switched world
|
||||
* @param from
|
||||
* world player changed from
|
||||
* @param to
|
||||
* world player changed to
|
||||
*/
|
||||
public void onWorldChange(TabPlayer changed, String from, String to) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes server switch
|
||||
*
|
||||
* @param changed
|
||||
* player who switched server
|
||||
* @param from
|
||||
* server player changed from
|
||||
* @param to
|
||||
* server player changed to
|
||||
*/
|
||||
public void onServerChange(TabPlayer changed, String from, String to) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the packet send and returns true if packet should be cancelled
|
||||
*
|
||||
* @param receiver
|
||||
* player receiving packet
|
||||
* @param packet
|
||||
* received packet
|
||||
*/
|
||||
public void onDisplayObjective(TabPlayer receiver, PacketPlayOutScoreboardDisplayObjective packet) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes login packet, only available on BungeeCord
|
||||
*
|
||||
* @param packetReceiver
|
||||
* player receiving client reset packet
|
||||
*/
|
||||
public void onLoginPacket(TabPlayer packetReceiver) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the packet send
|
||||
*
|
||||
* @param receiver
|
||||
* player receiving packet
|
||||
* @param packet
|
||||
* received packet
|
||||
*/
|
||||
public void onObjective(TabPlayer receiver, PacketPlayOutScoreboardObjective packet) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the packet send and possibly modifies it
|
||||
*
|
||||
* @param receiver
|
||||
* player receiving packet
|
||||
* @param info
|
||||
* received packet
|
||||
*/
|
||||
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes raw packet sent by client
|
||||
*
|
||||
* @param sender
|
||||
* packet sender
|
||||
* @param packet
|
||||
* packet received
|
||||
* @return true if false should be cancelled, false if not
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes raw packet sent to client
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
* @param packet
|
||||
* the packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs refresh of specified player
|
||||
*
|
||||
* @param refreshed
|
||||
* player to refresh
|
||||
* @param force
|
||||
* if refresh should be forced despite refresh seemingly not needed
|
||||
*/
|
||||
public void refresh(TabPlayer refreshed, boolean force) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes vanish status change of player
|
||||
*
|
||||
* @param player
|
||||
* Player who changed vanish status
|
||||
*/
|
||||
public void onVanishStatusChange(TabPlayer player) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this feature as one using specified placeholders
|
||||
*
|
||||
* @param placeholders
|
||||
* placeholders to add as used in this feature
|
||||
*/
|
||||
public void addUsedPlaceholders(Collection<String> placeholders) {
|
||||
if (placeholders.isEmpty()) return;
|
||||
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if world or server is disabled, {@code false} if not
|
||||
*
|
||||
* @param server
|
||||
* server to check
|
||||
* @param world
|
||||
* world to check
|
||||
* @return {@code true} if feature should be disabled, {@code false} if not
|
||||
*/
|
||||
public boolean isDisabled(String server, String world) {
|
||||
boolean contains = contains(disabledWorlds, world);
|
||||
if (worldWhitelistMode) contains = !contains;
|
||||
if (contains) return true;
|
||||
contains = contains(disabledServers, server);
|
||||
if (serverWhitelistMode) contains = !contains;
|
||||
return contains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if list contains the specified element or element
|
||||
* ends with {@code "*"} and element meeting that requirement is present,
|
||||
* {@code false} otherwise.
|
||||
*
|
||||
* @param list
|
||||
* List to check
|
||||
* @param element
|
||||
* Element to find
|
||||
* @return {@code true} if element was found, {@code false} if not
|
||||
*/
|
||||
protected boolean contains(String[] list, String element) {
|
||||
if (element == null) return false;
|
||||
for (String s : list) {
|
||||
if (s.endsWith("*")) {
|
||||
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
|
||||
} else {
|
||||
if (element.equalsIgnoreCase(s)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if method with specified name is overridden
|
||||
* in the implementation, {@code false} if not
|
||||
*
|
||||
* @param method
|
||||
* Method to check
|
||||
* @return {@code true} if overridden, {@code false} if not
|
||||
*/
|
||||
public boolean overridesMethod(String method) {
|
||||
return methodOverrides.contains(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player is currently located in a server
|
||||
* or world, which is marked as disabled, {@code false} if not
|
||||
*
|
||||
* @param p
|
||||
* Player to check
|
||||
* @return {@code true} if player is in disabled server / world,
|
||||
* {@code false} if not
|
||||
*/
|
||||
public boolean isDisabledPlayer(TabPlayer p) {
|
||||
return disabledPlayers.contains(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds specified player into list of players in disabled
|
||||
* servers / worlds.
|
||||
*
|
||||
* @param p
|
||||
* Player to add
|
||||
*/
|
||||
public void addDisabledPlayer(TabPlayer p) {
|
||||
if (disabledPlayers.contains(p)) return;
|
||||
disabledPlayers.add(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes specified player from list of players in disabled
|
||||
* servers / worlds
|
||||
*
|
||||
* @param p
|
||||
* Player to check
|
||||
*/
|
||||
public void removeDisabledPlayer(TabPlayer p) {
|
||||
disabledPlayers.remove(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns display name of {@link #refresh(TabPlayer, boolean)}
|
||||
* called for this feature in /tab cpu
|
||||
*
|
||||
* @return Display name of refresh function in this feature
|
||||
*/
|
||||
public String getRefreshDisplayName() { return null; }
|
||||
|
||||
/**
|
||||
* Returns name of this feature displayed in /tab cpu
|
||||
*
|
||||
* @return name of this feature display in /tab cpu
|
||||
*/
|
||||
public abstract String getFeatureName();
|
||||
}
|
||||
@@ -1,158 +1,37 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.UUID;
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.protocol.Skin;
|
||||
import me.neznamy.tab.api.protocol.TabPacket;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* An interface representing a player
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface TabPlayer {
|
||||
|
||||
/**
|
||||
* Refreshes all visuals on the player
|
||||
*/
|
||||
void forceRefresh();
|
||||
|
||||
/**
|
||||
* Returns player's name
|
||||
*
|
||||
* @return Player's name
|
||||
*/
|
||||
String getName();
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns player's UUID
|
||||
*
|
||||
* @return Player's UUID
|
||||
*/
|
||||
UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Returns player's uuid used in TabList. This may only be different from real uuid if
|
||||
* TAB is installed on a proxy which does not rewrite UUIDs.
|
||||
*
|
||||
* @return player's uuid in TabList
|
||||
*/
|
||||
UUID getTablistId();
|
||||
|
||||
/**
|
||||
* Returns player's protocol version
|
||||
*
|
||||
* @return player's protocol version
|
||||
*/
|
||||
ProtocolVersion getVersion();
|
||||
@NotNull UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Returns platform-specific entity
|
||||
*
|
||||
* @return an instance of bukkit/bungee player depending on platform
|
||||
* @return platform's player object
|
||||
*/
|
||||
Object getPlayer();
|
||||
|
||||
/**
|
||||
* Returns player's current world name (on BungeeCord this requires bridge installed)
|
||||
*
|
||||
* @return name of world where player is currently in, "N/A" on BungeeCord if bridge is not installed
|
||||
*/
|
||||
String getWorld();
|
||||
|
||||
/**
|
||||
* Returns player's current server name on BungeeCord (on bukkit this returns "N/A")
|
||||
*
|
||||
* @return name of server where player is currently in, "N/A" on bukkit
|
||||
*/
|
||||
String getServer();
|
||||
|
||||
/**
|
||||
* Performs platform-specific API call to check for permission and returns the result
|
||||
*
|
||||
* @param permission
|
||||
* the permission to check for
|
||||
* @return true if player has permission, false if not
|
||||
*/
|
||||
boolean hasPermission(String permission);
|
||||
|
||||
/**
|
||||
* Sends the player a custom universal packet
|
||||
*
|
||||
* @param packet
|
||||
* packet to send
|
||||
*/
|
||||
void sendCustomPacket(TabPacket packet);
|
||||
|
||||
/**
|
||||
* Sends the player a custom universal packet and adds that packet into counter that
|
||||
* is displayed in /tab cpu
|
||||
*
|
||||
* @param packet
|
||||
* packet to send
|
||||
* @param feature
|
||||
* feature to increment sent packet counter of
|
||||
*/
|
||||
void sendCustomPacket(TabPacket packet, TabFeature feature);
|
||||
|
||||
/**
|
||||
* Sends the player a custom universal packet and adds that packet into counter that
|
||||
* is displayed in /tab cpu
|
||||
*
|
||||
* @param packet
|
||||
* packet to send
|
||||
* @param feature
|
||||
* feature to increment sent packet counter of
|
||||
*/
|
||||
void sendCustomPacket(TabPacket packet, String feature);
|
||||
|
||||
/**
|
||||
* Sends the player a platform-specific packet
|
||||
*
|
||||
* @param packet
|
||||
* an instance of packet depending on platform
|
||||
*/
|
||||
void sendPacket(Object packet);
|
||||
|
||||
/**
|
||||
* Returns player's property by name
|
||||
*
|
||||
* @param name
|
||||
* name of property
|
||||
* @return the property or {@code null} if not found
|
||||
*/
|
||||
Property getProperty(String name);
|
||||
|
||||
/**
|
||||
* Sends a message to the player
|
||||
*
|
||||
* @param message
|
||||
* message to be sent
|
||||
* @param translateColors
|
||||
* whether colors should be translated or not
|
||||
*/
|
||||
void sendMessage(String message, boolean translateColors);
|
||||
|
||||
/**
|
||||
* Sends specified component as a chat message
|
||||
*
|
||||
* @param message
|
||||
* message to send
|
||||
*/
|
||||
void sendMessage(IChatBaseComponent message);
|
||||
|
||||
/**
|
||||
* Returns player's ping calculated by server
|
||||
*
|
||||
* @return player's ping
|
||||
*/
|
||||
int getPing();
|
||||
|
||||
/**
|
||||
* Returns player's skin data
|
||||
*
|
||||
* @return player's skin
|
||||
*/
|
||||
Skin getSkin();
|
||||
@NotNull Object getPlayer();
|
||||
|
||||
/**
|
||||
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
|
||||
@@ -161,92 +40,6 @@ public interface TabPlayer {
|
||||
*/
|
||||
boolean isLoaded();
|
||||
|
||||
/**
|
||||
* Sets property with specified name to new value. If property did not exist before, it is
|
||||
* created and {@code true} is returned. If it existed, it is overridden and {@code true} is returned.
|
||||
* {@code false} is returned otherwise.
|
||||
*
|
||||
* @param feature
|
||||
* feature using this property to get placeholders registered
|
||||
* @param identifier
|
||||
* property name
|
||||
* @param rawValue
|
||||
* new raw value
|
||||
* @return {@code true} if value changed / did not exist, {@code false} if value did not change
|
||||
*/
|
||||
boolean setProperty(TabFeature feature, String identifier, String rawValue);
|
||||
|
||||
/**
|
||||
* Loads property from config using standard property loading algorithm
|
||||
*
|
||||
* @param property
|
||||
* property name to load
|
||||
* @return {@code true} if value did not exist or changed, {@code false} otherwise
|
||||
*/
|
||||
boolean loadPropertyFromConfig(TabFeature feature, String property);
|
||||
|
||||
/**
|
||||
* Loads property from config using standard property loading algorithm. If the property is
|
||||
* not set in config, {@code ifNotSet} value is used.
|
||||
*
|
||||
* @param property
|
||||
* property name to load
|
||||
* @param ifNotSet
|
||||
* value to use if property is not defined in config
|
||||
* @return {@code true} if value did not exist or changed, {@code false} otherwise
|
||||
*/
|
||||
boolean loadPropertyFromConfig(TabFeature feature, String property, String ifNotSet);
|
||||
|
||||
/**
|
||||
* Returns name of player's scoreboard team or {@code null} if NameTag feature is disabled
|
||||
*
|
||||
* @return name of player's team
|
||||
*/
|
||||
String getTeamName();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player is disguised using LibsDisguises, {@code false} if not
|
||||
*
|
||||
* @return {@code true} if player is disguised, {@code false} if not
|
||||
*/
|
||||
boolean isDisguised();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has invisibility potion, {@code false} if not.
|
||||
* For bukkit, API is used, for BungeeCord, bridge is used.
|
||||
*
|
||||
* @return {@code true} if player has invisibility potion, {@code false} if not
|
||||
*/
|
||||
boolean hasInvisibilityPotion();
|
||||
|
||||
/**
|
||||
* Checks if player is vanished using any supported vanish plugin and returns the result.
|
||||
*
|
||||
* @return {@code true} if player is vanished, {@code false} if not
|
||||
*/
|
||||
boolean isVanished();
|
||||
|
||||
/**
|
||||
* Calls platform-specific method and returns the result
|
||||
*
|
||||
* @return {@code true} if player is online, {@code false} if not
|
||||
*/
|
||||
boolean isOnline();
|
||||
|
||||
/**
|
||||
* Returns GameMode of the player (0 for survival, 1 creative, 2 adventure, 3 spectator)
|
||||
*
|
||||
* @return GameMode of the player
|
||||
*/
|
||||
int getGamemode();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this is a bedrock player, {@code false} if not
|
||||
*
|
||||
* @return {@code true} if player is a bedrock player, {@code false} if not
|
||||
*/
|
||||
boolean isBedrockPlayer();
|
||||
|
||||
/**
|
||||
* Returns player's primary permission group. If group has been changed using
|
||||
* {@link #setTemporaryGroup(String)}, returns that value. Otherwise, returns group
|
||||
@@ -254,45 +47,48 @@ public interface TabPlayer {
|
||||
*
|
||||
* @return player's primary permission group
|
||||
*/
|
||||
String getGroup();
|
||||
@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()
|
||||
* @see #resetTemporaryGroup()
|
||||
*/
|
||||
void setTemporaryGroup(String group);
|
||||
void setTemporaryGroup(@Nullable String group);
|
||||
|
||||
/**
|
||||
* Returns temporary group applied to the player using {@link #setTemporaryGroup(String)}.
|
||||
* If no group was set, returns {@code null}
|
||||
* 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 Temporary group assigned to player or {@code null} if not set
|
||||
* @return {@code true} if group is set, {@code false} if not
|
||||
* @see #setTemporaryGroup(String)
|
||||
* @see #resetTemporaryGroup()
|
||||
*/
|
||||
boolean hasTemporaryGroup();
|
||||
|
||||
/**
|
||||
* Resets temporary group assigned using {@link #setTemporaryGroup(String)}.
|
||||
* If no temporary group is set, doesn't do anything.
|
||||
* 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.
|
||||
*
|
||||
* @see #setTemporaryGroup(String)
|
||||
* @see #hasTemporaryGroup()
|
||||
* @param profileName
|
||||
* New expected profile name
|
||||
*/
|
||||
void resetTemporaryGroup();
|
||||
void setExpectedProfileName(@NonNull String profileName);
|
||||
|
||||
/**
|
||||
* Returns player's game profile name as seen by other players. By default,
|
||||
* this returns player's name. If using a nickname / disguise plugin that changes
|
||||
* game profile name for other players and change was successfully detected, returns
|
||||
* that value.
|
||||
* 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 game profile name as seen by other players
|
||||
* @return Player's expected profile name
|
||||
*/
|
||||
String getNickname();
|
||||
@NotNull
|
||||
String getExpectedProfileName();
|
||||
}
|
||||
@@ -4,13 +4,27 @@ package me.neznamy.tab.api.bossbar;
|
||||
* An enum representing all available BossBar colors.
|
||||
* Calling ordinal() will return color's network ID.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public enum BarColor {
|
||||
|
||||
/** Pink color */
|
||||
PINK,
|
||||
|
||||
/** Blue color */
|
||||
BLUE,
|
||||
|
||||
/** Red color */
|
||||
RED,
|
||||
|
||||
/** Green color */
|
||||
GREEN,
|
||||
|
||||
/** Yellow color */
|
||||
YELLOW,
|
||||
|
||||
/** Purple color */
|
||||
PURPLE,
|
||||
|
||||
/** White color */
|
||||
WHITE
|
||||
}
|
||||
@@ -1,22 +1,24 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* An enum representing all available BossBar styles.
|
||||
* Calling ordinal() will return style's network ID.
|
||||
* Style names are equal to NMS names.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@SuppressWarnings("unused") // API class
|
||||
public enum BarStyle {
|
||||
|
||||
PROGRESS("SOLID"),
|
||||
NOTCHED_6("SEGMENTED_6"),
|
||||
NOTCHED_10("SEGMENTED_10"),
|
||||
NOTCHED_12("SEGMENTED_12"),
|
||||
NOTCHED_20("SEGMENTED_20");
|
||||
/** Fully solid line */
|
||||
PROGRESS,
|
||||
|
||||
/** Style's name used in Bukkit API */
|
||||
@Getter private final String bukkitName;
|
||||
/** Line segmented into 6 parts */
|
||||
NOTCHED_6,
|
||||
|
||||
/** Line segmented into 10 parts */
|
||||
NOTCHED_10,
|
||||
|
||||
/** Line segmented into 12 parts */
|
||||
NOTCHED_12,
|
||||
|
||||
/** Line segmented into 20 parts */
|
||||
NOTCHED_20
|
||||
}
|
||||
@@ -3,7 +3,9 @@ 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.
|
||||
@@ -26,7 +28,7 @@ public interface BossBar {
|
||||
*
|
||||
* @return name of BossBar
|
||||
*/
|
||||
String getName();
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns randomly generated uuid of this BossBar used internally to match
|
||||
@@ -34,7 +36,7 @@ public interface BossBar {
|
||||
*
|
||||
* @return uuid of this BossBar
|
||||
*/
|
||||
UUID getUniqueId();
|
||||
@NotNull UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Changes BossBar title to specified string, supporting placeholders.
|
||||
@@ -48,7 +50,7 @@ public interface BossBar {
|
||||
* @param title
|
||||
* New title to display in this BossBar
|
||||
*/
|
||||
void setTitle(String title);
|
||||
void setTitle(@NonNull String title);
|
||||
|
||||
/**
|
||||
* Changes BossBar progress to specified string, supporting placeholders.
|
||||
@@ -64,7 +66,7 @@ public interface BossBar {
|
||||
* @param progress
|
||||
* New progress to use in this BossBar
|
||||
*/
|
||||
void setProgress(String progress);
|
||||
void setProgress(@NonNull String progress);
|
||||
|
||||
/**
|
||||
* Changes BossBar progress to specified value ranging from 0 to 100.
|
||||
@@ -90,7 +92,7 @@ public interface BossBar {
|
||||
* @param color
|
||||
* New color to use in this BossBar
|
||||
*/
|
||||
void setColor(String color);
|
||||
void setColor(@NonNull String color);
|
||||
|
||||
/**
|
||||
* Changes BossBar color to specified enum constant.
|
||||
@@ -100,7 +102,7 @@ public interface BossBar {
|
||||
* @param color
|
||||
* New color to use in this BossBar
|
||||
*/
|
||||
void setColor(BarColor color);
|
||||
void setColor(@NonNull BarColor color);
|
||||
|
||||
/**
|
||||
* Changes BossBar style to specified string, supporting placeholders.
|
||||
@@ -116,7 +118,7 @@ public interface BossBar {
|
||||
* @param style
|
||||
* New style to use in this BossBar
|
||||
*/
|
||||
void setStyle(String style);
|
||||
void setStyle(@NonNull String style);
|
||||
|
||||
/**
|
||||
* Changes BossBar style to specified enum constant.
|
||||
@@ -126,7 +128,7 @@ public interface BossBar {
|
||||
* @param style
|
||||
* New style to use in this BossBar
|
||||
*/
|
||||
void setStyle(BarStyle style);
|
||||
void setStyle(@NonNull BarStyle style);
|
||||
|
||||
/**
|
||||
* Returns current title of the BossBar in raw format. If it contains placeholders,
|
||||
@@ -134,7 +136,7 @@ public interface BossBar {
|
||||
*
|
||||
* @return title of the BossBar
|
||||
*/
|
||||
String getTitle();
|
||||
@NotNull String getTitle();
|
||||
|
||||
/**
|
||||
* Returns progress of the BossBar as a string, which is either entered string
|
||||
@@ -142,7 +144,7 @@ public interface BossBar {
|
||||
*
|
||||
* @return entered progress as a string
|
||||
*/
|
||||
String getProgress();
|
||||
@NotNull String getProgress();
|
||||
|
||||
/**
|
||||
* Returns color of the BossBar as a string, which is either entered string
|
||||
@@ -150,7 +152,7 @@ public interface BossBar {
|
||||
*
|
||||
* @return entered color as a string
|
||||
*/
|
||||
String getColor();
|
||||
@NotNull String getColor();
|
||||
|
||||
/**
|
||||
* Returns style of the BossBar as a string, which is either entered string
|
||||
@@ -158,7 +160,7 @@ public interface BossBar {
|
||||
*
|
||||
* @return entered style as a string
|
||||
*/
|
||||
String getStyle();
|
||||
@NotNull String getStyle();
|
||||
|
||||
/**
|
||||
* Registers this BossBar to specified player.
|
||||
@@ -168,7 +170,7 @@ public interface BossBar {
|
||||
* @param player
|
||||
* Player to register this BossBar to
|
||||
*/
|
||||
void addPlayer(TabPlayer player);
|
||||
void addPlayer(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Unregisters this BossBar from specified player.
|
||||
@@ -178,7 +180,7 @@ public interface BossBar {
|
||||
* @param player
|
||||
* Player to unregister this BossBar from
|
||||
*/
|
||||
void removePlayer(TabPlayer player);
|
||||
void removePlayer(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns list of players who can see this BossBar.
|
||||
@@ -189,19 +191,24 @@ public interface BossBar {
|
||||
*
|
||||
* @return List of players seeing this BossBar
|
||||
*/
|
||||
List<TabPlayer> getPlayers();
|
||||
@NotNull List<TabPlayer> getPlayers();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if specified player can see this BossBar, {@code false} if not
|
||||
* 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 for
|
||||
* @return {@code true} if contains, {@code false} if not
|
||||
* 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 bossbar in configured as announcement-only bar,
|
||||
* 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
|
||||
|
||||
@@ -2,10 +2,12 @@ package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
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
|
||||
@@ -15,6 +17,7 @@ import me.neznamy.tab.api.TabPlayer;
|
||||
* This requires the BossBar feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface BossBarManager {
|
||||
|
||||
/**
|
||||
@@ -31,7 +34,7 @@ public interface BossBarManager {
|
||||
* Style to use
|
||||
* @return The newly created BossBar
|
||||
*/
|
||||
BossBar createBossBar(String title, float progress, BarColor color, BarStyle style);
|
||||
@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,
|
||||
@@ -50,30 +53,19 @@ public interface BossBarManager {
|
||||
* a placeholder that returns one of them
|
||||
* @return The newly created BossBar
|
||||
*/
|
||||
BossBar createBossBar(String title, String progress, String color, String style);
|
||||
@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 null if no such BossBar exists.
|
||||
* Returns {@code null} if no such BossBar exists.
|
||||
*
|
||||
* @param name
|
||||
* Name of registered BossBar
|
||||
* @return BossBar with specified name
|
||||
*/
|
||||
BossBar getBossBar(String name);
|
||||
|
||||
/**
|
||||
* Returns registered BossBar with specified UUID. This UUID is randomly generated when
|
||||
* BossBar is created and used in packets / to match which the one from Bukkit API.
|
||||
* Returns null if no such BossBar exists.
|
||||
*
|
||||
* @param id
|
||||
* UUID to return BossBar by
|
||||
* @return BossBar with specified uuid
|
||||
*/
|
||||
BossBar getBossBar(UUID id);
|
||||
@Nullable BossBar getBossBar(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Returns a map of all registered BossBars. This includes both BossBars from config
|
||||
@@ -84,7 +76,7 @@ public interface BossBarManager {
|
||||
*
|
||||
* @return all registered BossBars
|
||||
*/
|
||||
Map<String, BossBar> getRegisteredBossBars();
|
||||
@NotNull Map<String, BossBar> getRegisteredBossBars();
|
||||
|
||||
/**
|
||||
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
|
||||
@@ -96,7 +88,7 @@ public interface BossBarManager {
|
||||
* @see #hasBossBarVisible(TabPlayer)
|
||||
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
void toggleBossBar(TabPlayer player, boolean sendToggleMessage);
|
||||
void toggleBossBar(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns true if player can see BossBars, false if toggled via command or API.
|
||||
@@ -107,7 +99,7 @@ public interface BossBarManager {
|
||||
* @see #toggleBossBar(TabPlayer, boolean)
|
||||
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
boolean hasBossBarVisible(TabPlayer player);
|
||||
boolean hasBossBarVisible(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sets BossBar visibility of player to set value. If value did not change, nothing happens.
|
||||
@@ -122,7 +114,7 @@ public interface BossBarManager {
|
||||
* @see #toggleBossBar(TabPlayer, boolean)
|
||||
* @see #hasBossBarVisible(TabPlayer)
|
||||
*/
|
||||
void setBossBarVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
|
||||
void setBossBarVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Temporarily displays registered BossBar to player for specified amount of time in milliseconds.
|
||||
@@ -138,7 +130,7 @@ public interface BossBarManager {
|
||||
* @see #announceBossBar(String, int)
|
||||
* @see #getAnnouncedBossBars()
|
||||
*/
|
||||
void sendBossBarTemporarily(TabPlayer player, String bossBar, int duration);
|
||||
void sendBossBarTemporarily(@NonNull TabPlayer player, @NonNull String bossBar, int duration);
|
||||
|
||||
/**
|
||||
* Temporarily displays registered BossBar to all players for specified amount of time in milliseconds.
|
||||
@@ -152,7 +144,7 @@ public interface BossBarManager {
|
||||
* @see #sendBossBarTemporarily(TabPlayer, String, int)
|
||||
* @see #getAnnouncedBossBars()
|
||||
*/
|
||||
void announceBossBar(String bossBar, int duration);
|
||||
void announceBossBar(@NonNull String bossBar, int duration);
|
||||
|
||||
/**
|
||||
* Returns list of BossBars which are currently being announced.
|
||||
@@ -161,5 +153,5 @@ public interface BossBarManager {
|
||||
* @see #sendBossBarTemporarily(TabPlayer, String, int)
|
||||
* @see #announceBossBar(String, int)
|
||||
*/
|
||||
List<BossBar> getAnnouncedBossBars();
|
||||
@NotNull List<BossBar> getAnnouncedBossBars();
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* Class for click event action in chat component
|
||||
*/
|
||||
@Data
|
||||
public class ChatClickable {
|
||||
|
||||
/** Click action */
|
||||
@NonNull private final EnumClickAction action;
|
||||
|
||||
/** Click value */
|
||||
@NonNull private final String value;
|
||||
|
||||
/**
|
||||
* Enum for all possible click actions
|
||||
*/
|
||||
public enum EnumClickAction {
|
||||
|
||||
OPEN_URL,
|
||||
RUN_COMMAND,
|
||||
CHANGE_PAGE, //since 1.8
|
||||
SUGGEST_COMMAND,
|
||||
COPY_TO_CLIPBOARD //since 1.15
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChatComponentEntity extends IChatBaseComponent {
|
||||
|
||||
private final String CONTENTS;
|
||||
private final String VALUE_1_13;
|
||||
private final String VALUE_1_12;
|
||||
private final String VALUE_1_8;
|
||||
|
||||
public ChatComponentEntity(@NonNull String type, @NonNull UUID id, String name) {
|
||||
this.CONTENTS = String.format("{\"type\":\"%s\",\"id\":\"%s\",\"name\":{\"text\":\"%s\"}}", type, id, name);
|
||||
this.VALUE_1_13 = String.format("{type:\"%s\",id:\"%s\",name:\"{\\\"text\\\":\\\"%s\\\"}\"}", type, id, name);
|
||||
this.VALUE_1_12 = String.format("{type:\"%s\",id:\"%s\",name:\"%s\"}", type, id, name);
|
||||
this.VALUE_1_8 = String.format("{type:%s,id:%s,name:%s}", type, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toRawText() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) return CONTENTS;
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) return VALUE_1_13;
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() == 12) return VALUE_1_12;
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) return VALUE_1_8;
|
||||
throw new IllegalStateException("show_entity hover action is not supported on <1.8");
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* Class for hover event action in chat component
|
||||
*/
|
||||
@Data
|
||||
public class ChatHoverable {
|
||||
|
||||
/** Hover action */
|
||||
@NonNull private final EnumHoverAction action;
|
||||
|
||||
/** Hover value */
|
||||
@NonNull private final IChatBaseComponent value;
|
||||
|
||||
/**
|
||||
* Enum for all possible hover actions
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public enum EnumHoverAction {
|
||||
|
||||
SHOW_TEXT("contents"),
|
||||
SHOW_ITEM("value"),
|
||||
SHOW_ENTITY("contents");
|
||||
|
||||
@Getter private final String preferredKey;
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.chat.ChatClickable.EnumClickAction;
|
||||
import me.neznamy.tab.api.chat.ChatHoverable.EnumHoverAction;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data @NoArgsConstructor
|
||||
public class ChatModifier {
|
||||
|
||||
private TextColor color;
|
||||
private Boolean bold;
|
||||
private Boolean italic;
|
||||
private Boolean underlined;
|
||||
private Boolean strikethrough;
|
||||
private Boolean obfuscated;
|
||||
private ChatClickable clickEvent;
|
||||
private ChatHoverable hoverEvent;
|
||||
private String font;
|
||||
private ProtocolVersion targetVersion;
|
||||
|
||||
public ChatModifier(@NonNull ChatModifier modifier) {
|
||||
this.color = modifier.color == null ? null : new TextColor(modifier.color);
|
||||
this.bold = modifier.bold;
|
||||
this.italic = modifier.italic;
|
||||
this.underlined = modifier.underlined;
|
||||
this.strikethrough = modifier.strikethrough;
|
||||
this.obfuscated = modifier.obfuscated;
|
||||
this.clickEvent = modifier.clickEvent == null ? null : new ChatClickable(modifier.clickEvent.getAction(), modifier.clickEvent.getValue());
|
||||
this.hoverEvent = modifier.hoverEvent == null ? null : new ChatHoverable(modifier.hoverEvent.getAction(), modifier.hoverEvent.getValue());
|
||||
this.font = modifier.font;
|
||||
this.targetVersion = modifier.targetVersion;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if bold is defined and set to true, false otherwise
|
||||
*
|
||||
* @return true if bold is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isBold() {
|
||||
return Boolean.TRUE.equals(bold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if italic is defined and set to true, false otherwise
|
||||
*
|
||||
* @return true if italic is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isItalic() {
|
||||
return Boolean.TRUE.equals(italic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if underlined is defined and set to true, false otherwise
|
||||
*
|
||||
* @return true if underlined is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isUnderlined() {
|
||||
return Boolean.TRUE.equals(underlined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if strikethrough is defined and set to true, false otherwise
|
||||
*
|
||||
* @return true if strikethrough is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isStrikethrough() {
|
||||
return Boolean.TRUE.equals(strikethrough);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if obfuscation is defined and set to true, false otherwise
|
||||
*
|
||||
* @return true if obfuscation is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isObfuscated() {
|
||||
return Boolean.TRUE.equals(obfuscated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click action to OPEN_URL and url to given value
|
||||
*
|
||||
* @param url
|
||||
* url to open
|
||||
*/
|
||||
public void onClickOpenUrl(@NonNull String url) {
|
||||
clickEvent = new ChatClickable(EnumClickAction.OPEN_URL, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click action to RUN_COMMAND and command to given value
|
||||
*
|
||||
* @param command
|
||||
* command to perform, might be without / to send a chat message
|
||||
*/
|
||||
public void onClickRunCommand(@NonNull String command) {
|
||||
clickEvent = new ChatClickable(EnumClickAction.RUN_COMMAND, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click action to SUGGEST_COMMAND and command to given value
|
||||
*
|
||||
* @param command
|
||||
* command to suggest
|
||||
*/
|
||||
public void onClickSuggestCommand(@NonNull String command) {
|
||||
clickEvent = new ChatClickable(EnumClickAction.SUGGEST_COMMAND, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click action to CHANGE_PAGE and page id to given value
|
||||
*
|
||||
* @param newPage
|
||||
* id of new page
|
||||
*/
|
||||
public void onClickChangePage(int newPage) {
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() < 8) throw new UnsupportedOperationException("change_page click action is not supported on <1.8");
|
||||
clickEvent = new ChatClickable(EnumClickAction.CHANGE_PAGE, String.valueOf(newPage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click action to COPY_TO_CLIPBOARD and text to provided value
|
||||
*
|
||||
* @param text
|
||||
* text to copy to clipboard on click
|
||||
*/
|
||||
public void onClickCopyToClipBoard(@NonNull String text) {
|
||||
clickEvent = new ChatClickable(EnumClickAction.COPY_TO_CLIPBOARD, text);
|
||||
}
|
||||
|
||||
public void onClick(@NonNull EnumClickAction action, @NonNull String value) {
|
||||
clickEvent = new ChatClickable(action, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets hover action to SHOW_TEXT and text to given value
|
||||
*
|
||||
* @param text
|
||||
* text to show
|
||||
*/
|
||||
public void onHoverShowText(@NonNull IChatBaseComponent text) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_TEXT, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets hover action to SHOW_ITEM and item to given value
|
||||
*
|
||||
* @param serializedItem
|
||||
* item to show
|
||||
*/
|
||||
public void onHoverShowItem(@NonNull String serializedItem) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ITEM, new IChatBaseComponent(serializedItem));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets hover action to SHOW_ENTITY and entity data to given values
|
||||
*
|
||||
* @param type
|
||||
* entity type
|
||||
* @param id
|
||||
* entity uuid
|
||||
* @param name
|
||||
* entity custom name
|
||||
*/
|
||||
public void onHoverShowEntity(@NonNull String type, @NonNull UUID id, String name) {
|
||||
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
|
||||
}
|
||||
|
||||
public void onHover(@NonNull EnumHoverAction action, @NonNull IChatBaseComponent value) {
|
||||
hoverEvent = new ChatHoverable(action, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public JSONObject serialize() {
|
||||
JSONObject json = new JSONObject();
|
||||
if (color != null) json.put("color", targetVersion.getMinorVersion() >= 16 ? color.toString() : color.getLegacyColor().toString().toLowerCase());
|
||||
if (bold != null) json.put("bold", bold);
|
||||
if (italic != null) json.put("italic", italic);
|
||||
if (underlined != null) json.put("underlined", underlined);
|
||||
if (strikethrough != null) json.put("strikethrough", strikethrough);
|
||||
if (obfuscated != null) json.put("obfuscated", obfuscated);
|
||||
if (clickEvent != null) {
|
||||
JSONObject click = new JSONObject();
|
||||
click.put("action", clickEvent.getAction().toString().toLowerCase());
|
||||
click.put("value", clickEvent.getValue());
|
||||
json.put("clickEvent", click);
|
||||
}
|
||||
if (hoverEvent != null) {
|
||||
JSONObject hover = new JSONObject();
|
||||
hover.put("action", hoverEvent.getAction().toString().toLowerCase());
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
|
||||
hover.put(hoverEvent.getAction().getPreferredKey(), hoverEvent.getValue());
|
||||
} else {
|
||||
hover.put("value", TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 ?
|
||||
hoverEvent.getValue() : hoverEvent.getValue().toRawText());
|
||||
}
|
||||
json.put("hoverEvent", hover);
|
||||
}
|
||||
if (font != null) json.put("font", font);
|
||||
return json;
|
||||
}
|
||||
|
||||
public String getMagicCodes() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (isBold()) builder.append(EnumChatFormat.BOLD.getFormat());
|
||||
if (isItalic()) builder.append(EnumChatFormat.ITALIC.getFormat());
|
||||
if (isUnderlined()) builder.append(EnumChatFormat.UNDERLINE.getFormat());
|
||||
if (isStrikethrough()) builder.append(EnumChatFormat.STRIKETHROUGH.getFormat());
|
||||
if (isObfuscated()) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class representing a chat component that was serialized before,
|
||||
* but deserialize was requested in PacketPlayOutPlayerInfo packet.
|
||||
* This is used to read and modify the packet and then rewrite it.
|
||||
* However, display name components must be deserialized to properly
|
||||
* forward them in the new packet, but its value is not actually
|
||||
* used anywhere.
|
||||
* <p>
|
||||
* To avoid this heavy and completely unnecessary operation, the original
|
||||
* string is saved and returned during serialization. If any read/write
|
||||
* operation is performed, the string is fully deserialized and this object
|
||||
* will act like a normal chat component.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@RequiredArgsConstructor
|
||||
public class DeserializedChatComponent extends IChatBaseComponent {
|
||||
|
||||
/** The original serialized component string */
|
||||
@NonNull private final String json;
|
||||
|
||||
/**
|
||||
* Flag tracking whether this component was fully deserialized
|
||||
* in case of a read operation to avoid repeating the deserialization
|
||||
* process on every request.
|
||||
*/
|
||||
private boolean deserialized;
|
||||
|
||||
/**
|
||||
* Flag tracking whether this component was modified, which means
|
||||
* a new string has to be created instead of returning the input one.
|
||||
*/
|
||||
private boolean modified;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (modified) return super.toString();
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(ProtocolVersion clientVersion) {
|
||||
if (modified) return super.toString(clientVersion);
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IChatBaseComponent> getExtra() {
|
||||
if (!deserialized) deserialize();
|
||||
return super.getExtra();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
if (!deserialized) deserialize();
|
||||
return super.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ChatModifier getModifier() {
|
||||
if (!deserialized) deserialize();
|
||||
return super.getModifier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
|
||||
if (!deserialized) deserialize();
|
||||
modified = true;
|
||||
return super.setExtra(components);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtra(@NotNull IChatBaseComponent child) {
|
||||
if (!deserialized) deserialize();
|
||||
modified = true;
|
||||
super.addExtra(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModifier(@NotNull ChatModifier modifier) {
|
||||
if (!deserialized) deserialize();
|
||||
modified = true;
|
||||
super.setModifier(modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a full deserialization process on this component.
|
||||
*/
|
||||
void deserialize() {
|
||||
deserialized = true;
|
||||
if (json.startsWith("\"") && json.endsWith("\"") && json.length() > 1) {
|
||||
//simple component with only text used, minecraft serializer outputs the text in quotes instead of full json
|
||||
setText(json.substring(1, json.length()-1));
|
||||
return;
|
||||
}
|
||||
JSONObject jsonObject;
|
||||
try {
|
||||
jsonObject = (JSONObject) new JSONParser().parse(json);
|
||||
} catch (ParseException e) {
|
||||
TabAPI.getInstance().logError("Failed to deserialize json component " + json, e);
|
||||
return;
|
||||
}
|
||||
/* if (jsonObject.containsKey("type")) {
|
||||
return new ChatComponentEntity((String) jsonObject.get("type"), UUID.fromString((String) jsonObject.get("id")), IChatBaseComponent.deserialize(jsonObject.get("name").toString()).toFlatText());
|
||||
}*/
|
||||
setText((String) jsonObject.get("text"));
|
||||
getModifier().setBold(getBoolean(jsonObject, "bold"));
|
||||
getModifier().setItalic(getBoolean(jsonObject, "italic"));
|
||||
getModifier().setUnderlined(getBoolean(jsonObject, "underlined"));
|
||||
getModifier().setStrikethrough(getBoolean(jsonObject, "strikethrough"));
|
||||
getModifier().setObfuscated(getBoolean(jsonObject, "obfuscated"));
|
||||
getModifier().setColor(TextColor.fromString(((String) jsonObject.get("color"))));
|
||||
if (jsonObject.containsKey("clickEvent")) {
|
||||
JSONObject clickEvent = (JSONObject) jsonObject.get("clickEvent");
|
||||
String action = (String) clickEvent.get("action");
|
||||
String value = clickEvent.get("value").toString();
|
||||
getModifier().onClick(ChatClickable.EnumClickAction.valueOf(action.toUpperCase()), value);
|
||||
}
|
||||
if (jsonObject.containsKey("hoverEvent")) {
|
||||
JSONObject hoverEvent = (JSONObject) jsonObject.get("hoverEvent");
|
||||
String action = (String) hoverEvent.get("action");
|
||||
String value = (String) hoverEvent.get("value");
|
||||
getModifier().onHover(ChatHoverable.EnumHoverAction.valueOf(action.toUpperCase()), deserialize(value));
|
||||
}
|
||||
if (jsonObject.containsKey("extra")) {
|
||||
List<Object> list = (List<Object>) jsonObject.get("extra");
|
||||
for (Object extra : list) {
|
||||
String string = extra.toString();
|
||||
//reverting .toString() removing "" for simple text
|
||||
if (!string.startsWith("{")) string = "\"" + string + "\"";
|
||||
addExtra(IChatBaseComponent.deserialize(string));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns boolean value of requested key from map
|
||||
*
|
||||
* @param jsonObject
|
||||
* map to get value from
|
||||
* @param key
|
||||
* name of key
|
||||
* @return value from json object or null if not present
|
||||
*/
|
||||
private static Boolean getBoolean(@NonNull JSONObject jsonObject, @NonNull String key) {
|
||||
String value = String.valueOf(jsonObject.getOrDefault(key, null));
|
||||
return "null".equals(value) ? null : Boolean.parseBoolean(value);
|
||||
}
|
||||
}
|
||||
@@ -1,373 +0,0 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.api.util.ComponentCache;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.IChatBaseComponent class to make work with it much easier
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@NoArgsConstructor
|
||||
public class IChatBaseComponent {
|
||||
|
||||
/**
|
||||
* Component cache maps to avoid large memory allocations as well as
|
||||
* higher CPU usage when using animations which send the same text on repeat.
|
||||
*/
|
||||
private static final ComponentCache<String, IChatBaseComponent> stringCache = new ComponentCache<>(10000, (text, clientVersion) -> {
|
||||
return text.contains("#") || text.contains("&x") || text.contains(EnumChatFormat.COLOR_CHAR + "x") || text.contains("<") ?
|
||||
IChatBaseComponent.fromColoredText(text) : //contains RGB colors
|
||||
new IChatBaseComponent(text); //no RGB
|
||||
});
|
||||
private static final ComponentCache<IChatBaseComponent, String> serializeCache = new ComponentCache<>(10000,
|
||||
(component, clientVersion) -> component.toString());
|
||||
|
||||
/** Text of the component */
|
||||
@Getter @Setter private String text;
|
||||
|
||||
/** Chat modifier containing color, magic codes, hover and click event */
|
||||
@Getter @Setter @NonNull private ChatModifier modifier = new ChatModifier();
|
||||
|
||||
/** Extra components used in "extra" field */
|
||||
private List<IChatBaseComponent> extra;
|
||||
|
||||
/**
|
||||
* Constructs a new component which is a clone of provided component
|
||||
*
|
||||
* @param component
|
||||
* component to clone
|
||||
*/
|
||||
public IChatBaseComponent(@NonNull IChatBaseComponent component) {
|
||||
this.text = component.text;
|
||||
this.modifier = new ChatModifier(component.modifier);
|
||||
for (IChatBaseComponent child : component.getExtra()) {
|
||||
addExtra(new IChatBaseComponent(child));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given text
|
||||
*
|
||||
* @param text
|
||||
* text to display
|
||||
*/
|
||||
public IChatBaseComponent(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of extra components. If no extra components are defined, returns empty list.
|
||||
*
|
||||
* @return list of extra components
|
||||
*/
|
||||
public List<IChatBaseComponent> getExtra() {
|
||||
if (extra == null) return Collections.emptyList();
|
||||
return extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets full list of extra components to given list. Does not allow empty list.
|
||||
*
|
||||
* @param components
|
||||
* components to use as extra
|
||||
* @return self
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code components} is an empty list
|
||||
*/
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
|
||||
if (components.isEmpty()) throw new IllegalArgumentException("Unexpected empty array of components"); //exception taken from minecraft
|
||||
this.extra = components;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends provided component as extra component
|
||||
*
|
||||
* @param child
|
||||
* component to append
|
||||
*/
|
||||
public void addExtra(@NonNull IChatBaseComponent child) {
|
||||
if (extra == null) extra = new ArrayList<>();
|
||||
extra.add(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes string and returns created component. If provided string is null, returns null.
|
||||
*
|
||||
* @param json
|
||||
* serialized string
|
||||
* @return Deserialized component or null if input is null
|
||||
*/
|
||||
public static IChatBaseComponent deserialize(String json) {
|
||||
if (json == null) return null;
|
||||
return new DeserializedChatComponent(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the component to a string representing the serialized component.
|
||||
* This method is only used internally by json library since it's missing
|
||||
* protocol version field used by the method.
|
||||
*
|
||||
* @return serialized component in string form
|
||||
* @see #toString(ProtocolVersion)
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
JSONObject json = new JSONObject();
|
||||
if (text != null) json.put("text", text);
|
||||
if (modifier.getTargetVersion() == null) modifier.setTargetVersion(TabAPI.getInstance().getServerVersion()); //packet.toString() was called as a part of a debug message
|
||||
json.putAll(modifier.serialize());
|
||||
if (extra != null) json.put("extra", extra);
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this component with colors based on client version.
|
||||
* If client version is <1.16, HEX colors will be converted to legacy colors.
|
||||
*
|
||||
* @param clientVersion
|
||||
* client version to adapt component for
|
||||
* @return serialized string
|
||||
*/
|
||||
public String toString(ProtocolVersion clientVersion) {
|
||||
return toString(clientVersion, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this component with colors based on client version.
|
||||
* If client version is <1.16, HEX colors will be converted to legacy colors.
|
||||
*
|
||||
* @param clientVersion
|
||||
* client version to adapt component for
|
||||
* @param sendTranslatableIfEmpty
|
||||
* whether empty translatable should be sent if text is empty or not
|
||||
* @return serialized string
|
||||
*/
|
||||
public String toString(ProtocolVersion clientVersion, boolean sendTranslatableIfEmpty) {
|
||||
if (extra == null) {
|
||||
if (text == null) return null;
|
||||
if (text.length() == 0) {
|
||||
if (sendTranslatableIfEmpty) {
|
||||
return "{\"translate\":\"\"}";
|
||||
} else {
|
||||
return "{\"text\":\"\"}";
|
||||
}
|
||||
}
|
||||
}
|
||||
modifier.setTargetVersion(clientVersion);
|
||||
for (IChatBaseComponent child : getExtra()) {
|
||||
child.modifier.setTargetVersion(clientVersion);
|
||||
}
|
||||
return serializeCache.get(this, clientVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns organized component from colored text
|
||||
*
|
||||
* @param originalText
|
||||
* text to convert
|
||||
* @return organized component from colored text
|
||||
*/
|
||||
public static IChatBaseComponent fromColoredText(@NonNull String originalText) {
|
||||
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText));
|
||||
List<IChatBaseComponent> components = new ArrayList<>();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
IChatBaseComponent component = new IChatBaseComponent();
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (c == EnumChatFormat.COLOR_CHAR) {
|
||||
i++;
|
||||
if (i >= text.length()) {
|
||||
break;
|
||||
}
|
||||
c = text.charAt(i);
|
||||
if ((c >= 'A') && (c <= 'Z')) {
|
||||
c = (char)(c + ' ');
|
||||
}
|
||||
EnumChatFormat format = EnumChatFormat.getByChar(c);
|
||||
if (format != null) {
|
||||
if (builder.length() > 0) {
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
component = new IChatBaseComponent(component);
|
||||
component.text = null;
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
switch (format) {
|
||||
case BOLD:
|
||||
component.modifier.setBold(true);
|
||||
break;
|
||||
case ITALIC:
|
||||
component.modifier.setItalic(true);
|
||||
break;
|
||||
case UNDERLINE:
|
||||
component.modifier.setUnderlined(true);
|
||||
break;
|
||||
case STRIKETHROUGH:
|
||||
component.modifier.setStrikethrough(true);
|
||||
break;
|
||||
case OBFUSCATED:
|
||||
component.modifier.setObfuscated(true);
|
||||
break;
|
||||
case RESET:
|
||||
component = new IChatBaseComponent();
|
||||
component.modifier.setColor(new TextColor(EnumChatFormat.WHITE));
|
||||
break;
|
||||
default:
|
||||
component = new IChatBaseComponent();
|
||||
component.modifier.setColor(new TextColor(format));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (c == '#' && text.length() > i+6) {
|
||||
String hex = text.substring(i+1, i+7);
|
||||
if (RGBUtils.getInstance().isHexCode(hex)) {
|
||||
TextColor color;
|
||||
if (containsLegacyCode(text, i)) {
|
||||
color = new TextColor(hex, EnumChatFormat.getByChar(text.charAt(i+8)));
|
||||
i += 8;
|
||||
} else {
|
||||
color = new TextColor(hex);
|
||||
i += 6;
|
||||
}
|
||||
if (builder.length() > 0) {
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
component = new IChatBaseComponent();
|
||||
component.modifier.setColor(color);
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
return new IChatBaseComponent("").setExtra(components);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if text contains legacy color request at defined RGB index start
|
||||
*
|
||||
* @param text
|
||||
* text to check
|
||||
* @param i
|
||||
* current index start
|
||||
* @return true if legacy color is defined, false if not
|
||||
*/
|
||||
private static boolean containsLegacyCode(String text, int i) {
|
||||
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
|
||||
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this component into a simple text with legacy colors (the closest match if color is set to RGB)
|
||||
*
|
||||
* @return The simple text format
|
||||
*/
|
||||
public String toLegacyText() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
append(builder, "");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends text to string builder, might also add color and magic codes if they are different
|
||||
* from previous component in chain.
|
||||
*
|
||||
* @param builder
|
||||
* builder to append text to
|
||||
* @param previousFormatting
|
||||
* colors and magic codes in previous component
|
||||
* @return new formatting, might be identical to previous one
|
||||
*/
|
||||
private String append(StringBuilder builder, String previousFormatting) {
|
||||
String formatting = previousFormatting;
|
||||
if (text != null) {
|
||||
formatting = getFormatting();
|
||||
if (!formatting.equals(previousFormatting)) {
|
||||
builder.append(formatting);
|
||||
}
|
||||
builder.append(text);
|
||||
|
||||
}
|
||||
for (IChatBaseComponent component : getExtra()) {
|
||||
formatting = component.append(builder, formatting);
|
||||
}
|
||||
return formatting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns colors and magic codes of this component
|
||||
*
|
||||
* @return used colors and magic codes
|
||||
*/
|
||||
private String getFormatting() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (modifier.getColor() != null) {
|
||||
if (modifier.getColor().getLegacyColor() == EnumChatFormat.WHITE) {
|
||||
//preventing unwanted &r -> &f conversion and stopping the <1.13 client bug fix from working
|
||||
builder.append(EnumChatFormat.RESET.getFormat());
|
||||
} else {
|
||||
builder.append(modifier.getColor().getLegacyColor().getFormat());
|
||||
}
|
||||
}
|
||||
builder.append(modifier.getMagicCodes());
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns raw text without colors, only works correctly when component is organized
|
||||
*
|
||||
* @return raw text in this component and all child components
|
||||
*/
|
||||
public String toRawText() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (text != null) builder.append(text);
|
||||
for (IChatBaseComponent child : getExtra()) {
|
||||
if (child.text != null) builder.append(child.text);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the component into flat text with used colors (including rgb) and magic codes
|
||||
*
|
||||
* @return converted text
|
||||
*/
|
||||
public String toFlatText() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (modifier.getColor() != null) builder.append("#").append(modifier.getColor().getHexCode());
|
||||
builder.append(modifier.getMagicCodes());
|
||||
if (text != null) builder.append(text);
|
||||
for (IChatBaseComponent child : getExtra()) {
|
||||
builder.append(child.toFlatText());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most optimized component based on text. Returns null if text is null,
|
||||
* organized component if RGB colors are used or simple component with only text field
|
||||
* containing the whole text when no RGB colors are used
|
||||
*
|
||||
* @param text
|
||||
* text to create component from
|
||||
* @return The most performance-optimized component based on text
|
||||
*/
|
||||
public static IChatBaseComponent optimizedComponent(String text) {
|
||||
return stringCache.get(text, null);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class extending IChatBaseComponent that holds the original value
|
||||
* to avoid deserializing it, which is an unnecessary operation that
|
||||
* slows down the performance, as well as makes code more complicated
|
||||
* and open to problems.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class WrappedChatComponent extends IChatBaseComponent {
|
||||
|
||||
/** Original NMS component */
|
||||
@Getter private final Object originalComponent;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(ProtocolVersion clientVersion) {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IChatBaseComponent> getExtra() {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ChatModifier getModifier() {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExtra(@NonNull IChatBaseComponent child) {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModifier(@NotNull ChatModifier modifier) {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package me.neznamy.tab.api.chat.rgb.format;
|
||||
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
/**
|
||||
* Call to MiniMessage API to reformat text to &x&R&R&G&G&B&B
|
||||
*/
|
||||
public class MiniMessageFormat implements RGBFormatter {
|
||||
|
||||
@Override
|
||||
public String reformat(String text) {
|
||||
if (!text.contains("<")) return text;
|
||||
try {
|
||||
return LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(text));
|
||||
} catch (Throwable ignored) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package me.neznamy.tab.api.chat.rgb.format;
|
||||
|
||||
/**
|
||||
* Formatter for &#RRGGBB
|
||||
*/
|
||||
public class UnnamedFormat1 implements RGBFormatter {
|
||||
|
||||
@Override
|
||||
public String reformat(String text) {
|
||||
return text.contains("&#") ? text.replace("&#", "#") : text;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package me.neznamy.tab.api.chat.rgb.gradient;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.TextColor;
|
||||
|
||||
/**
|
||||
* Gradient applier for <gradient:#RRGGBB:#RRGGBB>Text</gradient>
|
||||
* and <gradient:#RRGGBB|L:#RRGGBB>Text</gradient>
|
||||
*/
|
||||
public class KyoriGradient implements GradientPattern {
|
||||
|
||||
private final Pattern pattern = Pattern.compile("<gradient:#[0-9a-fA-F]{6}:#[0-9a-fA-F]{6}>[^<]*</gradient>");
|
||||
|
||||
private final Pattern patternLegacy = Pattern.compile("<gradient:#[0-9a-fA-F]{6}\\|.:#[0-9a-fA-F]{6}>[^<]*</gradient>");
|
||||
|
||||
@Override
|
||||
public String applyPattern(String text, boolean ignorePlaceholders) {
|
||||
if (!text.contains("<grad")) return text;
|
||||
String replaced = text;
|
||||
Matcher m = patternLegacy.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String format = m.group();
|
||||
EnumChatFormat legacyColor = EnumChatFormat.getByChar(format.charAt(18));
|
||||
if ((ignorePlaceholders && format.contains("%")) || legacyColor == null) continue;
|
||||
TextColor start = new TextColor(format.substring(11, 17), legacyColor);
|
||||
String message = format.substring(28, format.length()-11);
|
||||
TextColor end = new TextColor(format.substring(21, 27));
|
||||
String applied = asGradient(start, message, end);
|
||||
replaced = replaced.replace(format, applied);
|
||||
}
|
||||
m = pattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String format = m.group();
|
||||
if (ignorePlaceholders && format.contains("%")) continue;
|
||||
TextColor start = new TextColor(format.substring(11, 17));
|
||||
String message = format.substring(26, format.length()-11);
|
||||
TextColor end = new TextColor(format.substring(19, 25));
|
||||
String applied = asGradient(start, message, end);
|
||||
replaced = replaced.replace(format, applied);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
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.
|
||||
*
|
||||
* All methods in this class that are annotated with {@link Subscribe}
|
||||
* <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>
|
||||
@@ -24,7 +27,7 @@ public interface EventBus {
|
||||
* @param listener the listener to register
|
||||
* @throws RuntimeException if a method does not meet the criteria
|
||||
*/
|
||||
void register(Object listener);
|
||||
void register(@NonNull Object listener);
|
||||
|
||||
/**
|
||||
* Registers the given handler to this event bus.
|
||||
@@ -33,7 +36,7 @@ public interface EventBus {
|
||||
* @param handler the handler to register
|
||||
* @param <E> the type of the event
|
||||
*/
|
||||
<E extends TabEvent> void register(Class<E> type, EventHandler<E> handler);
|
||||
<E extends TabEvent> void register(@NonNull Class<E> type, @NonNull EventHandler<E> handler);
|
||||
|
||||
/**
|
||||
* Scans the given listener for any method that is annotated with
|
||||
@@ -41,7 +44,7 @@ public interface EventBus {
|
||||
*
|
||||
* @param listener the listener to unregister
|
||||
*/
|
||||
void unregister(Object listener);
|
||||
void unregister(@NonNull Object listener);
|
||||
|
||||
/**
|
||||
* Unregisters the given handler from this event bus.
|
||||
@@ -49,5 +52,5 @@ public interface EventBus {
|
||||
* @param handler the handler to unregister
|
||||
* @param <E> the type of the event
|
||||
*/
|
||||
<E extends TabEvent> void unregister(EventHandler<E> handler);
|
||||
<E extends TabEvent> void unregister(@NonNull EventHandler<E> handler);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package me.neznamy.tab.api.event;
|
||||
/**
|
||||
* An event that may be fired by TAB when something occurs.
|
||||
*
|
||||
* This is a marker interface. All events <strong>must</strong> implement this
|
||||
* <p>This is a marker interface. All events <strong>must</strong> implement this
|
||||
* interface.
|
||||
*/
|
||||
public interface TabEvent {
|
||||
|
||||
@@ -2,6 +2,7 @@ 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.
|
||||
@@ -13,13 +14,13 @@ public interface PlayerLoadEvent extends TabEvent {
|
||||
*
|
||||
* @return the player that was loaded
|
||||
*/
|
||||
TabPlayer getPlayer();
|
||||
@NotNull TabPlayer getPlayer();
|
||||
|
||||
/**
|
||||
* Returns true if player was loaded because they joined,
|
||||
* false if the player was loaded because tab reload command was executed
|
||||
* Returns {@code true} if player was loaded because they joined,
|
||||
* {@code false} if the player was loaded because tab reload command was executed
|
||||
*
|
||||
* @return true if player joined, false if plugin was reloaded
|
||||
* @return {@code true} if player joined, {@code false} if plugin was reloaded
|
||||
*/
|
||||
boolean isJoin();
|
||||
}
|
||||
@@ -1,11 +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 me.neznamy.tab.api.placeholder.Placeholder;
|
||||
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 {
|
||||
|
||||
String getIdentifier();
|
||||
/**
|
||||
* Returns placeholder identifier starting and ending with {@code %}
|
||||
*
|
||||
* @return placeholder identifier starting and ending with {@code %}
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
|
||||
void setPlaceholder(Placeholder placeholder);
|
||||
/**
|
||||
* 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,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();
|
||||
}
|
||||
@@ -1,14 +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();
|
||||
|
||||
String getIdentifier();
|
||||
|
||||
void enableTriggerMode(Runnable onActivation, Runnable onDisable);
|
||||
|
||||
void unload();
|
||||
|
||||
boolean isUsed();
|
||||
/**
|
||||
* Returns placeholder's identifier
|
||||
*
|
||||
* @return placeholder's identifier
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
}
|
||||
@@ -1,53 +1,93 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
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 java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
ServerPlaceholder registerServerPlaceholder(String identifier, int refresh, Supplier<Object> supplier);
|
||||
|
||||
/**
|
||||
* Registers a player placeholder (placeholder with player-specific output)
|
||||
*/
|
||||
PlayerPlaceholder registerPlayerPlaceholder(String identifier, int refresh, Function<TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Registers a relational placeholder (different output for each player pair)
|
||||
*/
|
||||
RelationalPlaceholder registerRelationalPlaceholder(String identifier, int refresh, BiFunction<TabPlayer, TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Detects placeholders in text using %% pattern and returns list of all detected identifiers
|
||||
*
|
||||
* @param text
|
||||
* text to detect placeholders in
|
||||
* @return list of detected identifiers
|
||||
*/
|
||||
List<String> detectPlaceholders(String text);
|
||||
|
||||
/**
|
||||
* Adds placeholder to list of used placeholders and assigns this feature as using it,
|
||||
* which will then receive refresh() if values changes
|
||||
*
|
||||
* @param identifier
|
||||
* placeholder identifier
|
||||
* @param feature
|
||||
* feature using the placeholder
|
||||
* 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
|
||||
*/
|
||||
void addUsedPlaceholder(String identifier, TabFeature feature);
|
||||
@NotNull ServerPlaceholder registerServerPlaceholder(@NonNull String identifier, int refresh, @NonNull Supplier<Object> supplier);
|
||||
|
||||
String findReplacement(String placeholder, String output);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
List<String> getUsedPlaceholders();
|
||||
/**
|
||||
* 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);
|
||||
|
||||
Placeholder getPlaceholder(String identifier);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -1,28 +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 {
|
||||
|
||||
void updateValue(TabPlayer player, Object value);
|
||||
|
||||
/**
|
||||
* Calls the placeholder request function and returns the output.
|
||||
* If the placeholder threw an exception, it is logged in {@code placeholder-errors.log}
|
||||
* file and "ERROR" is returned.
|
||||
*
|
||||
* @param p
|
||||
* player to get placeholder value for
|
||||
* @return value placeholder returned or "ERROR" if it threw an error
|
||||
*/
|
||||
Object request(TabPlayer p);
|
||||
|
||||
/**
|
||||
* Returns last known value for given player.
|
||||
* 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 get last value for
|
||||
* @return last known value for entered player
|
||||
* Player to update value for
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
String getLastValue(TabPlayer player);
|
||||
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);
|
||||
}
|
||||
@@ -1,33 +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 {
|
||||
|
||||
void updateValue(TabPlayer viewer, TabPlayer target, Object value);
|
||||
|
||||
/**
|
||||
* Calls the placeholder request function and returns the output.
|
||||
* If the placeholder threw an exception, it is logged in {@code placeholder-errors.log}
|
||||
* file and "ERROR" is returned.
|
||||
* 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
|
||||
* player looking at output of the placeholder
|
||||
* Placeholder viewer
|
||||
* @param target
|
||||
* player the placeholder is displayed on
|
||||
* @return value placeholder returned or "ERROR" if it threw an error
|
||||
* Placeholder target
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
Object request(TabPlayer viewer, TabPlayer target);
|
||||
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Returns last known value for given players. First player is viewer,
|
||||
* second player is target.
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*
|
||||
* @param viewer
|
||||
* viewer of the placeholder
|
||||
* Placeholder viewer
|
||||
* @param target
|
||||
* target who is the text displayed on
|
||||
* @return last known value for entered player duo
|
||||
* Placeholder target
|
||||
*/
|
||||
String getLastValue(TabPlayer viewer, TabPlayer target);
|
||||
void update(@NonNull TabPlayer viewer, @NonNull TabPlayer target);
|
||||
}
|
||||
@@ -1,22 +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 {
|
||||
|
||||
void updateValue(Object value);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Calls the placeholder request function and returns the output.
|
||||
* If the placeholder threw an exception, it is logged in {@code placeholder-errors.log}
|
||||
* file and "ERROR" is returned.
|
||||
*
|
||||
* @return value placeholder returned or "ERROR" if it threw an error
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*/
|
||||
Object request();
|
||||
|
||||
/**
|
||||
* Returns last known value of the placeholder.
|
||||
*
|
||||
* @return last known value
|
||||
*/
|
||||
String getLastValue();
|
||||
void update();
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.api.util.BiFunctionWithException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A class for packet building, methods are overridden in
|
||||
* platform-specific builders which extend this class to build actual packets.
|
||||
* Packets which don't have a method override are forwarded into
|
||||
* sendPacket method, where they are handled using platform's API instead.
|
||||
*/
|
||||
public class PacketBuilder {
|
||||
|
||||
/** Function map turning custom packet class into platform-specific packets */
|
||||
protected final Map<Class<? extends TabPacket>, BiFunctionWithException<TabPacket, ProtocolVersion, Object>> buildMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance and fills {@link #buildMap} with abstract build methods
|
||||
* of packets present on all platforms.
|
||||
*/
|
||||
public PacketBuilder() {
|
||||
buildMap.put(PacketPlayOutBoss.class, (packet, version) -> build((PacketPlayOutBoss)packet, version));
|
||||
buildMap.put(PacketPlayOutChat.class, (packet, version) -> build((PacketPlayOutChat)packet, version));
|
||||
buildMap.put(PacketPlayOutPlayerInfo.class, (packet, version) -> build((PacketPlayOutPlayerInfo)packet, version));
|
||||
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> build((PacketPlayOutPlayerListHeaderFooter)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> build((PacketPlayOutScoreboardDisplayObjective)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> build((PacketPlayOutScoreboardObjective)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> build((PacketPlayOutScoreboardScore)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> build((PacketPlayOutScoreboardTeam)packet, version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts custom packet into platform-specific packet by calling a function from
|
||||
* {@link #buildMap}.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to build
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if reflection fails
|
||||
*/
|
||||
public Object build(@NonNull TabPacket packet, @NonNull ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return buildMap.get(packet.getClass()).apply(packet, clientVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutBoss class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutChat class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
*/
|
||||
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutPlayerInfo class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutPlayerListHeaderFooter class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
*/
|
||||
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardDisplayObjective class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardObjective class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardScore class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardTeam class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cuts given string to specified character length (or length-1 if last character is a color character)
|
||||
* and translates RGB to legacy colors. If string is not that long, the original string is returned.
|
||||
* RGB codes are converted into legacy, since cutting is only needed for <1.13.
|
||||
* If {@code string} is {@code null}, empty string is returned.
|
||||
*
|
||||
* @param string
|
||||
* String to cut
|
||||
* @param length
|
||||
* Length to cut to
|
||||
* @return string cut to {@code length} characters
|
||||
*/
|
||||
public String cutTo(String string, int length) {
|
||||
if (string == null) return "";
|
||||
String legacyText = string;
|
||||
if (string.contains("#")) {
|
||||
//converting RGB to legacy colors
|
||||
legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
|
||||
}
|
||||
if (legacyText.length() <= length) return legacyText;
|
||||
if (legacyText.charAt(length-1) == EnumChatFormat.COLOR_CHAR) {
|
||||
return legacyText.substring(0, length-1); //cutting one extra character to prevent prefix ending with "&"
|
||||
} else {
|
||||
return legacyText.substring(0, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
|
||||
* it as a serialized component, which BungeeCord uses.
|
||||
* <p>
|
||||
* If {@code clientVersion} is < 1.12, the text is cut to {@code length} characters if
|
||||
* needed and returned.
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @param clientVersion
|
||||
* Version of player to convert text for
|
||||
* @return serialized component for 1.13+ clients, cut string for 1.12-
|
||||
*/
|
||||
public String jsonOrCut(String text, ProtocolVersion clientVersion, int length) {
|
||||
if (text == null) return null;
|
||||
if (clientVersion.getMinorVersion() >= 13) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
|
||||
} else {
|
||||
return cutTo(text, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts platform-specific instance of player info packet into
|
||||
* {@link PacketPlayOutPlayerInfo} object.
|
||||
*
|
||||
* @param packet
|
||||
* platform-specific info packet
|
||||
* @param clientVersion
|
||||
* Version of client receiving the packet
|
||||
* @return The packet converted into {@link PacketPlayOutPlayerInfo}
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo readPlayerInfo(Object packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts platform-specific instance of objective packet into
|
||||
* {@link PacketPlayOutScoreboardObjective} object.
|
||||
*
|
||||
* @param packet
|
||||
* platform-specific objective packet
|
||||
* @return The packet converted into {@link PacketPlayOutScoreboardObjective}
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public PacketPlayOutScoreboardObjective readObjective(Object packet) throws ReflectiveOperationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts platform-specific instance of display objective packet into
|
||||
* {@link PacketPlayOutScoreboardDisplayObjective} object.
|
||||
*
|
||||
* @param packet
|
||||
* platform-specific display objective packet
|
||||
* @return The packet converted into {@link PacketPlayOutScoreboardDisplayObjective}
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object packet) throws ReflectiveOperationException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
@Data
|
||||
public class PacketPlayOutBoss implements TabPacket {
|
||||
|
||||
/** UUID of the BossBar */
|
||||
private final UUID id;
|
||||
|
||||
/** Action of this packet */
|
||||
private Action action;
|
||||
|
||||
/** BossBar title */
|
||||
private String name;
|
||||
|
||||
/** BossBar progress (0-1)*/
|
||||
private float pct;
|
||||
|
||||
/** BossBar color */
|
||||
private BarColor color;
|
||||
|
||||
/** BossBar style */
|
||||
private BarStyle overlay;
|
||||
|
||||
/** Darken screen flag */
|
||||
private boolean darkenScreen;
|
||||
|
||||
/** Play music flag */
|
||||
private boolean playMusic;
|
||||
|
||||
/** Create fog flag */
|
||||
private boolean createWorldFog;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.ADD action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param name
|
||||
* BossBar title
|
||||
* @param pct
|
||||
* BossBar progress
|
||||
* @param color
|
||||
* BossBar color
|
||||
* @param overlay
|
||||
* BossBar style
|
||||
*/
|
||||
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name, float pct, @NonNull BarColor color, @NonNull BarStyle overlay) {
|
||||
this.action = Action.ADD;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.pct = pct;
|
||||
this.color = color;
|
||||
this.overlay = overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#REMOVE} action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
*/
|
||||
public PacketPlayOutBoss(@NonNull UUID id) {
|
||||
this.action = Action.REMOVE;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PCT} action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param pct
|
||||
* BossBar progress
|
||||
*/
|
||||
public PacketPlayOutBoss(@NonNull UUID id, float pct) {
|
||||
this.action = Action.UPDATE_PCT;
|
||||
this.id = id;
|
||||
this.pct = pct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_NAME} action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param name
|
||||
* BossBar title
|
||||
*/
|
||||
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name) {
|
||||
this.action = Action.UPDATE_NAME;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_STYLE} action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param color
|
||||
* BossBar color
|
||||
* @param overlay
|
||||
* BossBar style
|
||||
*/
|
||||
public PacketPlayOutBoss(@NonNull UUID id, @NonNull BarColor color, @NonNull BarStyle overlay) {
|
||||
this.action = Action.UPDATE_STYLE;
|
||||
this.id = id;
|
||||
this.color = color;
|
||||
this.overlay = overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PROPERTIES} action
|
||||
*
|
||||
* @param darkenScreen
|
||||
* Darken screen flag
|
||||
* @param playMusic
|
||||
* Play music flag
|
||||
* @param createWorldFog
|
||||
* Create fog flag
|
||||
*/
|
||||
public PacketPlayOutBoss(@NonNull UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
|
||||
this.action = Action.UPDATE_PROPERTIES;
|
||||
this.id = id;
|
||||
this.darkenScreen = darkenScreen;
|
||||
this.playMusic = playMusic;
|
||||
this.createWorldFog = createWorldFog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bitmask based on {@link #darkenScreen}, {@link #playMusic} and {@link #darkenScreen} values.
|
||||
* <p>
|
||||
* {@link #darkenScreen} adds {@code 1}, {@link #playMusic} {@code 2} and {@link #darkenScreen} {@code 4}
|
||||
* to the final value.
|
||||
*
|
||||
* @return the bitmask
|
||||
*/
|
||||
public byte getFlags() {
|
||||
byte value = 0;
|
||||
if (darkenScreen) value += 1;
|
||||
if (playMusic) value += 2;
|
||||
if (createWorldFog) value += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum representing all valid boss packet actions.
|
||||
* Calling ordinal() will return action's network ID.
|
||||
*/
|
||||
public enum Action {
|
||||
|
||||
ADD,
|
||||
REMOVE,
|
||||
UPDATE_PCT,
|
||||
UPDATE_NAME,
|
||||
UPDATE_STYLE,
|
||||
UPDATE_PROPERTIES
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
@Data
|
||||
public class PacketPlayOutChat implements TabPacket {
|
||||
|
||||
/** Message to be sent */
|
||||
@NonNull private final IChatBaseComponent message;
|
||||
|
||||
/** Message position */
|
||||
@NonNull private final ChatMessageType type;
|
||||
|
||||
/**
|
||||
* An enum representing positions of a chat message
|
||||
* Calling ordinal() will return type's network ID.
|
||||
*/
|
||||
public enum ChatMessageType {
|
||||
|
||||
CHAT,
|
||||
SYSTEM,
|
||||
GAME_INFO
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
@Data @AllArgsConstructor
|
||||
public class PacketPlayOutPlayerInfo implements TabPacket {
|
||||
|
||||
/** Map for faster EnumSet creation */
|
||||
private static final EnumMap<EnumPlayerInfoAction, EnumSet<EnumPlayerInfoAction>> actionToEnumSetMap =
|
||||
new EnumMap<EnumPlayerInfoAction, EnumSet<EnumPlayerInfoAction>>(EnumPlayerInfoAction.class) {{
|
||||
for (EnumPlayerInfoAction action : EnumPlayerInfoAction.values()) {
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
EnumSet<EnumPlayerInfoAction> set = EnumSet.allOf(EnumPlayerInfoAction.class);
|
||||
set.remove(EnumPlayerInfoAction.REMOVE_PLAYER);
|
||||
put(action, set);
|
||||
} else {
|
||||
put(action, EnumSet.of(action));
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
/** Packet actions */
|
||||
private final EnumSet<EnumPlayerInfoAction> actions;
|
||||
|
||||
/** List of affected entries */
|
||||
private final List<PlayerInfoData> entries;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param action
|
||||
* Packet action
|
||||
* @param entries
|
||||
* Affected entries
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, PlayerInfoData... entries) {
|
||||
this(action, Arrays.asList(entries));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param action
|
||||
* Packet action
|
||||
* @param entries
|
||||
* Affected entries
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo(@NonNull EnumPlayerInfoAction action, @NonNull List<PlayerInfoData> entries) {
|
||||
this.actions = actionToEnumSetMap.get(action);
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param actions
|
||||
* Packet actions
|
||||
* @param entries
|
||||
* Affected entries
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo(@NonNull EnumSet<EnumPlayerInfoAction> actions, @NonNull PlayerInfoData... entries) {
|
||||
this.actions = actions;
|
||||
this.entries = Arrays.asList(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* A subclass representing player list entry
|
||||
*/
|
||||
@Data @AllArgsConstructor
|
||||
public static class PlayerInfoData {
|
||||
|
||||
/** Real name of affected player */
|
||||
private String name;
|
||||
|
||||
/** Player UUID */
|
||||
@NonNull private UUID uniqueId;
|
||||
|
||||
/** Player's skin, null for empty skin */
|
||||
private Skin skin;
|
||||
|
||||
/** I have no idea what this is */
|
||||
private boolean listed;
|
||||
|
||||
/** Latency */
|
||||
private int latency;
|
||||
|
||||
/** GameMode */
|
||||
private EnumGamemode gameMode = EnumGamemode.SURVIVAL; //ProtocolLib causes NPE even when action does not use GameMode
|
||||
|
||||
/**
|
||||
* Display name displayed in TabList. Using {@code null} results in no display name
|
||||
* and scoreboard team prefix/suffix being visible in TabList instead.
|
||||
*/
|
||||
private IChatBaseComponent displayName;
|
||||
|
||||
/** Chat session ID */
|
||||
private UUID chatSessionId;
|
||||
|
||||
/** Player's chat signing key */
|
||||
private Object profilePublicKey;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Suitable for
|
||||
* {@link EnumPlayerInfoAction#UPDATE_GAME_MODE} action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
* @param gameMode
|
||||
* Player's GameMode
|
||||
*/
|
||||
public PlayerInfoData(@NonNull UUID uniqueId, EnumGamemode gameMode) {
|
||||
this.uniqueId = uniqueId;
|
||||
this.gameMode = gameMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Suitable for
|
||||
* {@link EnumPlayerInfoAction#UPDATE_LATENCY} action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
* @param latency
|
||||
* Player's ping
|
||||
*/
|
||||
public PlayerInfoData(@NonNull UUID uniqueId, int latency) {
|
||||
this.uniqueId = uniqueId;
|
||||
this.latency = latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Suitable for
|
||||
* {@link EnumPlayerInfoAction#UPDATE_DISPLAY_NAME} action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
* @param displayName
|
||||
* Player's display name
|
||||
*/
|
||||
public PlayerInfoData(@NonNull UUID uniqueId, IChatBaseComponent displayName) {
|
||||
this.uniqueId = uniqueId;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter. Suitable for
|
||||
* {@link EnumPlayerInfoAction#REMOVE_PLAYER} action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
*/
|
||||
public PlayerInfoData(@NonNull UUID uniqueId) {
|
||||
this.uniqueId = uniqueId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* En enum representing packet action
|
||||
*/
|
||||
public enum EnumPlayerInfoAction {
|
||||
|
||||
ADD_PLAYER,
|
||||
INITIALIZE_CHAT,
|
||||
UPDATE_GAME_MODE,
|
||||
UPDATE_LISTED,
|
||||
UPDATE_LATENCY,
|
||||
UPDATE_DISPLAY_NAME,
|
||||
REMOVE_PLAYER
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum representing GameMode
|
||||
*/
|
||||
public enum EnumGamemode {
|
||||
|
||||
NOT_SET,
|
||||
SURVIVAL,
|
||||
CREATIVE,
|
||||
ADVENTURE,
|
||||
SPECTATOR;
|
||||
|
||||
/** Value array to iterate over to avoid array creations on each call */
|
||||
public static final EnumGamemode[] VALUES = values();
|
||||
}
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
@Data @AllArgsConstructor
|
||||
public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
|
||||
|
||||
/** TabList header */
|
||||
@NonNull private final IChatBaseComponent header;
|
||||
|
||||
/** TabList footer */
|
||||
@NonNull private final IChatBaseComponent footer;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. They are converted to {@link IChatBaseComponent}
|
||||
* using {@link IChatBaseComponent#optimizedComponent(String)} method.
|
||||
*
|
||||
* @param header
|
||||
* TabList header
|
||||
* @param footer
|
||||
* TabList footer
|
||||
*/
|
||||
public PacketPlayOutPlayerListHeaderFooter(@NonNull String header, @NonNull String footer) {
|
||||
this.header = IChatBaseComponent.optimizedComponent(header);
|
||||
this.footer = IChatBaseComponent.optimizedComponent(footer);
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
@Data
|
||||
public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
|
||||
|
||||
/**
|
||||
* Display slot.
|
||||
* 0 = PlayerList,
|
||||
* 1 = SideBar,
|
||||
* 2 = BelowName.
|
||||
*/
|
||||
private final int slot;
|
||||
|
||||
/** Up to 16 characters long objective name */
|
||||
@NonNull private final String objectiveName;
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
@Data @AllArgsConstructor
|
||||
public class PacketPlayOutScoreboardObjective implements TabPacket {
|
||||
|
||||
/**
|
||||
* Packet action.
|
||||
* 0 = register,
|
||||
* 1 = unregister,
|
||||
* 2 = update title
|
||||
*/
|
||||
private final int action;
|
||||
|
||||
/** Up to 16 characters long objective name */
|
||||
@NonNull private final String objectiveName;
|
||||
|
||||
/** Display name */
|
||||
private final String displayName;
|
||||
|
||||
/** Display type, only takes effect in PlayerList */
|
||||
private final EnumScoreboardHealthDisplay renderType;
|
||||
|
||||
/**
|
||||
* Constructs new packet with given objective name and 1 (unregister) action.
|
||||
*
|
||||
* @param objectiveName
|
||||
* objective name, up to 16 characters long
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code objectiveName} is null or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardObjective(@NonNull String objectiveName) {
|
||||
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
|
||||
this.objectiveName = objectiveName;
|
||||
this.displayName = ""; //avoiding NPE on <1.7
|
||||
this.action = 1;
|
||||
this.renderType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum representing available display types.
|
||||
* They only take effect in PlayerList position.
|
||||
*/
|
||||
public enum EnumScoreboardHealthDisplay {
|
||||
|
||||
INTEGER, HEARTS
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
@Data
|
||||
public class PacketPlayOutScoreboardScore implements TabPacket {
|
||||
|
||||
/** Packet action */
|
||||
@NonNull private final Action action;
|
||||
|
||||
/** Objective name */
|
||||
@NonNull private final String objectiveName;
|
||||
|
||||
/** Affected player */
|
||||
@NonNull private final String player;
|
||||
|
||||
/** Player's score */
|
||||
private final int score;
|
||||
|
||||
/**
|
||||
* An enum representing action types
|
||||
*/
|
||||
public enum Action {
|
||||
|
||||
CHANGE,
|
||||
REMOVE
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
@Data
|
||||
public class PacketPlayOutScoreboardTeam implements TabPacket {
|
||||
|
||||
/**
|
||||
* Up to 16 characters long team name.
|
||||
* It is used to sort players alphabetically in TabList by team names.
|
||||
* Team-less players appear on top, sorted by usernames.
|
||||
* Players with teams are below them, players of the same team are
|
||||
* sorted alphabetically by usernames.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
|
||||
/** Team prefix, limited to 16 characters, on <1.13 */
|
||||
private String playerPrefix;
|
||||
|
||||
/** Team suffix, limited to 16 characters, on <1.13 */
|
||||
private String playerSuffix;
|
||||
|
||||
/**
|
||||
* NameTag visibility rule since 1.8. Possible options are:
|
||||
* {@code always}, {@code hideForOtherTeams}, {@code hideForOwnTeam}, {@code never}
|
||||
*/
|
||||
private String nameTagVisibility;
|
||||
|
||||
/**
|
||||
* Collision rule added in 1.9. Possible options are:
|
||||
* {@code always}, {@code pushOtherTeams}, {@code pushOwnTeam}, {@code never}
|
||||
*/
|
||||
private String collisionRule;
|
||||
|
||||
/**
|
||||
* Team color field added in 1.13. It determines name color,
|
||||
* prefix and suffix color start.
|
||||
*/
|
||||
private EnumChatFormat color;
|
||||
|
||||
/**
|
||||
* Affected entities. For players, it is their name, for other entities their UUID
|
||||
* Player name length is limited to 16 characters for <1.8, 40 characters since 1.8.
|
||||
* Entity UUID option was added in 1.8.
|
||||
*/
|
||||
@NonNull private Collection<String> players = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* Packet action.
|
||||
* 0 = create team,
|
||||
* 1 = remove team,
|
||||
* 2 = update team info,
|
||||
* 3 = add entries,
|
||||
* 4 = remove entries
|
||||
*/
|
||||
private final int action;
|
||||
|
||||
/**
|
||||
* Bit mask.
|
||||
* 0x01: Allow friendly fire,
|
||||
* 0x02: Can see invisible players on the same team.
|
||||
*/
|
||||
private int options;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Private constructor used
|
||||
* internally to validate team name and set action.
|
||||
*
|
||||
* @param action
|
||||
* Packet action (0 = create team, 1 = remove team, 2 = update team info,
|
||||
* 3 = add entries, 4 = remove entries)
|
||||
* @param name
|
||||
* Team name, up to 16 characters long
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
private PacketPlayOutScoreboardTeam(int action, @NonNull String name) {
|
||||
Preconditions.checkMaxLength(name, 16, "team name");
|
||||
this.action = action;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 0 (CREATE) action
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @param prefix
|
||||
* Prefix of players in team
|
||||
* @param suffix
|
||||
* Suffix of players in team
|
||||
* @param visibility
|
||||
* NameTag visibility rule
|
||||
* @param collision
|
||||
* Collision rule
|
||||
* @param players
|
||||
* Affected entities
|
||||
* @param options
|
||||
* bitmask of team options
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix, String visibility,
|
||||
String collision, @NonNull Collection<String> players, int options) {
|
||||
this(0, team);
|
||||
this.playerPrefix = prefix;
|
||||
this.playerSuffix = suffix;
|
||||
this.nameTagVisibility = visibility;
|
||||
this.collisionRule = collision;
|
||||
this.players = players;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 1 (REMOVE) action
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team) {
|
||||
this(1, team);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 2 (UPDATE_TEAM_INFO) action
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @param prefix
|
||||
* Prefix of players in team
|
||||
* @param suffix
|
||||
* Suffix of players in team
|
||||
* @param visibility
|
||||
* NameTag visibility rule
|
||||
* @param collision
|
||||
* Collision rule
|
||||
* @param options
|
||||
* bitmask of team options
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix,
|
||||
String visibility, String collision, int options) {
|
||||
this(2, team);
|
||||
this.playerPrefix = prefix;
|
||||
this.playerSuffix = suffix;
|
||||
this.nameTagVisibility = visibility;
|
||||
this.collisionRule = collision;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 3 (ADD_PLAYERS) if {@code add} is {@code true},
|
||||
* 4 (REMOVE_PLAYERS) if {@code add} is {@code false} action.
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @param players
|
||||
* Affected entities
|
||||
* @param add
|
||||
* {@code true} if players should be added, {@code false} if removed from team
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team, @NonNull Collection<String> players, boolean add) {
|
||||
this(add ? 3 : 4, team);
|
||||
this.players = players;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* Class representing a minecraft skin as a value - signature pair.
|
||||
*/
|
||||
@Data @AllArgsConstructor
|
||||
public class Skin {
|
||||
|
||||
/** Skin value */
|
||||
@NonNull private final String value;
|
||||
|
||||
/** Skin signature, can be null */
|
||||
private final String signature;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
/**
|
||||
* Interface representing a packet that can be sent to client independently of platform.
|
||||
* It is technically empty, existence is only present to restrict type access
|
||||
* when sending packets to player, so {@link me.neznamy.tab.api.TabPlayer#sendCustomPacket(TabPacket)} cannot
|
||||
* be called with an actual packet.
|
||||
*/
|
||||
public interface TabPacket {
|
||||
|
||||
/**
|
||||
* Returns user-friendly string representation of the packet
|
||||
*
|
||||
* @return user-friendly string representation of the packet
|
||||
*/
|
||||
String toString();
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
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.
|
||||
@@ -13,7 +16,7 @@ public interface Line {
|
||||
*
|
||||
* @return raw text of this line
|
||||
*/
|
||||
String getText();
|
||||
@NotNull String getText();
|
||||
|
||||
/**
|
||||
* Changes text to new value. Supports placeholders, which are
|
||||
@@ -25,5 +28,5 @@ public interface Line {
|
||||
* @param text
|
||||
* Raw text to display in the line
|
||||
*/
|
||||
void setText(String text);
|
||||
void setText(@NonNull String text);
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -8,6 +11,7 @@ import java.util.List;
|
||||
* {@link ScoreboardManager#createScoreboard(String, String, List)},
|
||||
* for scoreboards from config {@link ScoreboardManager#getRegisteredScoreboards()}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface Scoreboard {
|
||||
|
||||
/**
|
||||
@@ -17,7 +21,7 @@ public interface Scoreboard {
|
||||
*
|
||||
* @return custom name of this scoreboard
|
||||
*/
|
||||
String getName();
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns raw title of this scoreboard. Placeholders stay in their
|
||||
@@ -26,7 +30,7 @@ public interface Scoreboard {
|
||||
* @return scoreboard title
|
||||
* @see #setTitle(String)
|
||||
*/
|
||||
String getTitle();
|
||||
@NotNull String getTitle();
|
||||
|
||||
/**
|
||||
* Sets title to specified value. Placeholders are refreshed automatically
|
||||
@@ -43,7 +47,7 @@ public interface Scoreboard {
|
||||
* New title to use with placeholder support
|
||||
* @see #getTitle()
|
||||
*/
|
||||
void setTitle(String title);
|
||||
void setTitle(@NonNull String title);
|
||||
|
||||
/**
|
||||
* Returns list of lines in this scoreboard in the order they appear
|
||||
@@ -54,7 +58,7 @@ public interface Scoreboard {
|
||||
* @see #addLine(String)
|
||||
* @see #removeLine(int)
|
||||
*/
|
||||
List<Line> getLines();
|
||||
@NotNull List<Line> getLines();
|
||||
|
||||
/**
|
||||
* Adds line with specified text on the bottom of scoreboard. Supports
|
||||
@@ -65,7 +69,7 @@ public interface Scoreboard {
|
||||
* @see #getLines()
|
||||
* @see #removeLine(int)
|
||||
*/
|
||||
void addLine(String text);
|
||||
void addLine(@NonNull String text);
|
||||
|
||||
/**
|
||||
* Removes line with specified index. Index starts at {@code 0} and ends at
|
||||
|
||||
@@ -3,7 +3,10 @@ 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,
|
||||
@@ -11,8 +14,9 @@ import me.neznamy.tab.api.TabPlayer;
|
||||
* <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 null otherwise.
|
||||
* the method will return {@code null} otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface ScoreboardManager {
|
||||
|
||||
/**
|
||||
@@ -39,7 +43,7 @@ public interface ScoreboardManager {
|
||||
* @throws NullPointerException
|
||||
* if {@code name} or {@code lines} is {@code null}
|
||||
*/
|
||||
Scoreboard createScoreboard(String name, String title, List<String> lines);
|
||||
@NotNull Scoreboard createScoreboard(@NonNull String name, @NonNull String title, @NonNull List<String> lines);
|
||||
|
||||
/**
|
||||
* Returns map of registered scoreboards via config and API.
|
||||
@@ -49,7 +53,7 @@ public interface ScoreboardManager {
|
||||
*
|
||||
* @return map of registered scoreboards
|
||||
*/
|
||||
Map<String, Scoreboard> getRegisteredScoreboards();
|
||||
@NotNull Map<String, Scoreboard> getRegisteredScoreboards();
|
||||
|
||||
/**
|
||||
* Displays scoreboard to defined player. This will disable all display
|
||||
@@ -66,7 +70,7 @@ public interface ScoreboardManager {
|
||||
* @see #hasCustomScoreboard(TabPlayer)
|
||||
* @see #resetScoreboard(TabPlayer)
|
||||
*/
|
||||
void showScoreboard(TabPlayer player, Scoreboard scoreboard);
|
||||
void showScoreboard(@NonNull TabPlayer player, @NonNull Scoreboard scoreboard);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has custom scoreboard set using
|
||||
@@ -79,7 +83,7 @@ public interface ScoreboardManager {
|
||||
* @see #showScoreboard(TabPlayer, Scoreboard)
|
||||
* @see #resetScoreboard(TabPlayer)
|
||||
*/
|
||||
boolean hasCustomScoreboard(TabPlayer player);
|
||||
boolean hasCustomScoreboard(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Hides custom scoreboard sent using {@link #showScoreboard(TabPlayer, Scoreboard)}
|
||||
@@ -91,7 +95,7 @@ public interface ScoreboardManager {
|
||||
* @see #showScoreboard(TabPlayer, Scoreboard)
|
||||
* @see #hasCustomScoreboard(TabPlayer)
|
||||
*/
|
||||
void resetScoreboard(TabPlayer player);
|
||||
void resetScoreboard(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has scoreboard enabled, {@code false}
|
||||
@@ -105,7 +109,7 @@ public interface ScoreboardManager {
|
||||
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
|
||||
* @see #toggleScoreboard(TabPlayer, boolean)
|
||||
*/
|
||||
boolean hasScoreboardVisible(TabPlayer player);
|
||||
boolean hasScoreboardVisible(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sets scoreboard visibility of player to defined value. If visibility status
|
||||
@@ -122,7 +126,7 @@ public interface ScoreboardManager {
|
||||
* @see #toggleScoreboard(TabPlayer, boolean)
|
||||
* @see #hasScoreboardVisible(TabPlayer)
|
||||
*/
|
||||
void setScoreboardVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
|
||||
void setScoreboardVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Toggles scoreboard for specified player. If player had scoreboard visible,
|
||||
@@ -136,7 +140,7 @@ public interface ScoreboardManager {
|
||||
* @see #hasScoreboardVisible(TabPlayer)
|
||||
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
void toggleScoreboard(TabPlayer player, boolean sendToggleMessage);
|
||||
void toggleScoreboard(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Temporarily displays scoreboard to all players for specified amount of
|
||||
@@ -151,7 +155,7 @@ public interface ScoreboardManager {
|
||||
* if no scoreboard was found with such name or {@code duration}
|
||||
* is < 0.
|
||||
*/
|
||||
void announceScoreboard(String scoreboard, int duration);
|
||||
void announceScoreboard(@NonNull String scoreboard, int duration);
|
||||
|
||||
/**
|
||||
* Returns player's currently displayed scoreboard. This can be either with
|
||||
@@ -162,5 +166,5 @@ public interface ScoreboardManager {
|
||||
* player to get active scoreboard of
|
||||
* @return player's active scoreboard or {@code null} if player has no scoreboard
|
||||
*/
|
||||
Scoreboard getActiveScoreboard(TabPlayer player);
|
||||
@Nullable Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
|
||||
}
|
||||
+13
-37
@@ -1,4 +1,9 @@
|
||||
package me.neznamy.tab.api;
|
||||
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.
|
||||
@@ -7,39 +12,40 @@ package me.neznamy.tab.api;
|
||||
* 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.
|
||||
* 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(TabPlayer player, String header);
|
||||
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.
|
||||
* 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(TabPlayer player, String footer);
|
||||
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.
|
||||
* changes its value. Use {@code null} to reset value back to original.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
@@ -48,35 +54,5 @@ public interface HeaderFooterManager {
|
||||
* @param footer
|
||||
* Raw footer value
|
||||
*/
|
||||
void setHeaderAndFooter(TabPlayer player, String header, String footer);
|
||||
|
||||
/**
|
||||
* Resets header value previously set using {@link #setHeader(TabPlayer, String)}
|
||||
* or {@link #setHeaderAndFooter(TabPlayer, String, String)} and restores internal
|
||||
* logic using configuration files.
|
||||
*
|
||||
* @param player
|
||||
* Player to reset header for
|
||||
*/
|
||||
void resetHeader(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resets footer value previously set using {@link #setFooter(TabPlayer, String)}
|
||||
* or {@link #setHeaderAndFooter(TabPlayer, String, String)} and restores internal
|
||||
* logic using configuration files.
|
||||
*
|
||||
* @param player
|
||||
* Player to reset header for
|
||||
*/
|
||||
void resetFooter(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resets values previously set using {@link #setHeader(TabPlayer, String)},
|
||||
* {@link #setFooter(TabPlayer, String)} or {@link #setHeaderAndFooter(TabPlayer, String, String)}
|
||||
* and restores internal logic using configuration files.
|
||||
*
|
||||
* @param player
|
||||
* Player to reset header and footer for
|
||||
*/
|
||||
void resetHeaderAndFooter(TabPlayer player);
|
||||
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);
|
||||
}
|
||||
+26
-61
@@ -1,92 +1,63 @@
|
||||
package me.neznamy.tab.api;
|
||||
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
|
||||
* An interface allowing to modify TabList display names
|
||||
* of players.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getTablistFormatManager()}.
|
||||
* Instance can be obtained using {@link TabAPI#getTabListFormatManager()}.
|
||||
* This requires the feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
public interface TablistFormatManager {
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface TabListFormatManager {
|
||||
|
||||
/**
|
||||
* Changes player's prefix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats.
|
||||
* 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 #resetPrefix(TabPlayer)
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
void setPrefix(TabPlayer player, String prefix);
|
||||
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
|
||||
|
||||
/**
|
||||
* Changes player's name to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats.
|
||||
* 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 #resetName(TabPlayer)
|
||||
* @see #getCustomName(TabPlayer)
|
||||
* @see #getOriginalName(TabPlayer)
|
||||
*/
|
||||
void setName(TabPlayer player, String customName);
|
||||
void setName(@NonNull TabPlayer player, @Nullable String customName);
|
||||
|
||||
/**
|
||||
* Changes player's suffix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats.
|
||||
* 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 #resetSuffix(TabPlayer)
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
void setSuffix(TabPlayer player, String suffix);
|
||||
|
||||
/**
|
||||
* Resets player's prefix previously assigned using {@link #setPrefix(TabPlayer, String)}
|
||||
* method. If nothing was set, this method will not do anything.
|
||||
*
|
||||
* @param player
|
||||
* player to reset prefix of
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
void resetPrefix(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resets player's custom name previously assigned using {@link #setName(TabPlayer, String)}
|
||||
* method. If nothing was set, this method will not do anything.
|
||||
*
|
||||
* @param player
|
||||
* player to reset name of
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getCustomName(TabPlayer)
|
||||
* @see #getOriginalName(TabPlayer)
|
||||
*/
|
||||
void resetName(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resets player's suffix previously assigned using {@link #setSuffix(TabPlayer, String)}
|
||||
* method. If nothing was set, this method will not do anything.
|
||||
*
|
||||
* @param player
|
||||
* player to reset suffix of
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
void resetSuffix(TabPlayer player);
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
/**
|
||||
* Returns prefix assigned using {@link #setPrefix(TabPlayer, String)} method.
|
||||
@@ -96,10 +67,9 @@ public interface TablistFormatManager {
|
||||
* player to get custom prefix of
|
||||
* @return player's custom prefix or {@code null} if not defined
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #resetPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
String getCustomPrefix(TabPlayer player);
|
||||
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns custom name assigned using {@link #setName(TabPlayer, String)} method.
|
||||
@@ -109,10 +79,9 @@ public interface TablistFormatManager {
|
||||
* player to get custom name of
|
||||
* @return player's custom name or {@code null} if not defined
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #resetName(TabPlayer)
|
||||
* @see #getOriginalName(TabPlayer)
|
||||
*/
|
||||
String getCustomName(TabPlayer player);
|
||||
@Nullable String getCustomName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns suffix assigned using {@link #setSuffix(TabPlayer, String)} method.
|
||||
@@ -122,10 +91,9 @@ public interface TablistFormatManager {
|
||||
* player to get custom suffix of
|
||||
* @return player's custom suffix or {@code null} if not defined
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #resetSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
String getCustomSuffix(TabPlayer player);
|
||||
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original prefix applied using plugin's internal logic.
|
||||
@@ -135,10 +103,9 @@ public interface TablistFormatManager {
|
||||
* player to get original prefix of
|
||||
* @return player's original prefix
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #resetPrefix(TabPlayer)
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
*/
|
||||
String getOriginalPrefix(TabPlayer player);
|
||||
@NotNull String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original name applied using plugin's internal logic.
|
||||
@@ -148,10 +115,9 @@ public interface TablistFormatManager {
|
||||
* player to get original name of
|
||||
* @return player's original name
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #resetName(TabPlayer)
|
||||
* @see #getCustomName(TabPlayer)
|
||||
*/
|
||||
String getOriginalName(TabPlayer player);
|
||||
@NotNull String getOriginalName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original suffix applied using plugin's internal logic.
|
||||
@@ -161,8 +127,7 @@ public interface TablistFormatManager {
|
||||
* player to get original suffix of
|
||||
* @return player's original suffix
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #resetSuffix(TabPlayer)
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
*/
|
||||
String getOriginalSuffix(TabPlayer player);
|
||||
@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);
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package me.neznamy.tab.api.task;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
|
||||
/**
|
||||
* Manager for scheduling and executing tasks in TAB's
|
||||
* main thread to avoid concurrency issues.
|
||||
*/
|
||||
public interface ThreadManager {
|
||||
|
||||
/**
|
||||
* Starts a task in new thread and measures how long it took to process
|
||||
*
|
||||
* @param feature
|
||||
* feature to add cpu usage to
|
||||
* @param type
|
||||
* usage type to add cpu usage to
|
||||
* @param task
|
||||
* the task
|
||||
*/
|
||||
Future<Void> runMeasuredTask(TabFeature feature, String type, Runnable task);
|
||||
|
||||
/**
|
||||
* Starts a task in new thread and measures how long it took to process
|
||||
*
|
||||
* @param feature
|
||||
* feature to add cpu usage to
|
||||
* @param type
|
||||
* usage type to add cpu usage to
|
||||
* @param task
|
||||
* the task
|
||||
*/
|
||||
Future<Void> runMeasuredTask(String feature, String type, Runnable task);
|
||||
|
||||
/**
|
||||
* Runs task in a new thread
|
||||
*
|
||||
* @param task
|
||||
* the task
|
||||
*/
|
||||
Future<Void> runTask(Runnable task);
|
||||
|
||||
/**
|
||||
* Starts a new task with defined repeat interval that measures cpu usage
|
||||
*
|
||||
* @param intervalMilliseconds
|
||||
* task interval
|
||||
* @param feature
|
||||
* feature to add cpu usage to
|
||||
* @param type
|
||||
* usage type to add cpu usage to
|
||||
* @param task
|
||||
* the task
|
||||
* @return Future representing the submitted task
|
||||
*/
|
||||
Future<?> startRepeatingMeasuredTask(int intervalMilliseconds, TabFeature feature, String type, Runnable task);
|
||||
|
||||
/**
|
||||
* Starts a new task with defined repeat interval
|
||||
*
|
||||
* @param intervalMilliseconds
|
||||
* task interval
|
||||
* @param task
|
||||
* the task
|
||||
* @return Future representing the submitted task
|
||||
*/
|
||||
Future<?> startRepeatingTask(int intervalMilliseconds, Runnable task);
|
||||
|
||||
/**
|
||||
* Runs task with a delay and measures how long it took to process
|
||||
*
|
||||
* @param delayMilliseconds
|
||||
* how long to run the task after
|
||||
* @param feature
|
||||
* feature to add cpu usage to
|
||||
* @param type
|
||||
* usage type to add cpu usage to
|
||||
* @param task
|
||||
* the task
|
||||
* @return Future representing the submitted task
|
||||
*/
|
||||
Future<?> runTaskLater(int delayMilliseconds, TabFeature feature, String type, Runnable task);
|
||||
|
||||
/**
|
||||
* Runs task with a delay
|
||||
*
|
||||
* @param delayMilliseconds
|
||||
* how long to run the task after
|
||||
* @param task
|
||||
* the task
|
||||
* @return Future representing the submitted task
|
||||
*/
|
||||
Future<?> runTaskLater(int delayMilliseconds, Runnable task);
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package me.neznamy.tab.api.team;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
public interface TeamManager {
|
||||
|
||||
/**
|
||||
* Makes player's NameTag globally invisible
|
||||
*
|
||||
* @param player
|
||||
* player to hide nametag of
|
||||
* @see #showNametag(TabPlayer)
|
||||
* @see #hasHiddenNametag(TabPlayer)
|
||||
*/
|
||||
void hideNametag(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Hides player's NameTag for specified player until it's shown again
|
||||
*
|
||||
* @param player
|
||||
* player to hide nametag of
|
||||
* @param viewer
|
||||
* player to hide NameTag for
|
||||
*/
|
||||
void hideNametag(TabPlayer player, TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Makes player's NameTag visible again
|
||||
*
|
||||
* @param player
|
||||
* player to show nametag of
|
||||
* @see #hideNametag(TabPlayer)
|
||||
* @see #hasHiddenNametag(TabPlayer)
|
||||
*/
|
||||
void showNametag(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Shows player's NameTag for specified viewer if it was hidden before
|
||||
*
|
||||
* @param player
|
||||
* player to show nametag of
|
||||
* @param viewer
|
||||
* player to show NameTag back for
|
||||
*/
|
||||
void showNametag(TabPlayer player, TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Return whether player has hidden NameTag or not
|
||||
*
|
||||
* @param player
|
||||
* player to check nametag visibility status of
|
||||
* @return Whether player has hidden NameTag or not
|
||||
* @see #hideNametag(TabPlayer)
|
||||
* @see #showNametag(TabPlayer)
|
||||
*/
|
||||
boolean hasHiddenNametag(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(TabPlayer player, 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(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(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(TabPlayer player);
|
||||
|
||||
/**
|
||||
* 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(TabPlayer player, 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
|
||||
*/
|
||||
String getForcedTeamName(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(TabPlayer player, 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
|
||||
*/
|
||||
Boolean getCollisionRule(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sends update team's properties packet of player's team to everyone
|
||||
*
|
||||
* @param player
|
||||
* player to update team data of
|
||||
*/
|
||||
void updateTeamData(TabPlayer player);
|
||||
|
||||
void setPrefix(TabPlayer player, String prefix);
|
||||
|
||||
void setSuffix(TabPlayer player, String suffix);
|
||||
|
||||
void resetPrefix(TabPlayer player);
|
||||
|
||||
void resetSuffix(TabPlayer player);
|
||||
|
||||
String getCustomPrefix(TabPlayer player);
|
||||
|
||||
String getCustomSuffix(TabPlayer player);
|
||||
|
||||
String getOriginalPrefix(TabPlayer player);
|
||||
|
||||
String getOriginalSuffix(TabPlayer player);
|
||||
|
||||
/**
|
||||
* Toggles nametag visibility view on all players for specified player.
|
||||
* On first call, nametags of all players will become invisible for specified
|
||||
* player. On second call, they will become visible again.
|
||||
*
|
||||
* @param player
|
||||
* player to toggle nametag visibility view for
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if configured toggle message should be sent, {@code false} if not
|
||||
*/
|
||||
void toggleNameTagVisibilityView(TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has hidden nametags 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(TabPlayer player);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package me.neznamy.tab.api.team;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
public interface UnlimitedNametagManager extends TeamManager {
|
||||
|
||||
void disableArmorStands(TabPlayer player);
|
||||
|
||||
void enableArmorStands(TabPlayer player);
|
||||
|
||||
boolean hasDisabledArmorStands(TabPlayer player);
|
||||
|
||||
void setName(TabPlayer player, String customName);
|
||||
|
||||
void setLine(TabPlayer player, String line, String value);
|
||||
|
||||
void resetName(TabPlayer player);
|
||||
|
||||
void resetLine(TabPlayer player, String line);
|
||||
|
||||
String getCustomName(TabPlayer player);
|
||||
|
||||
String getCustomLineValue(TabPlayer player, String line);
|
||||
|
||||
String getOriginalName(TabPlayer player);
|
||||
|
||||
String getOriginalLineValue(TabPlayer player, String line);
|
||||
|
||||
List<String> getDefinedLines();
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package me.neznamy.tab.api.util;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class ComponentCache<K, V> {
|
||||
|
||||
private final int cacheSize;
|
||||
private final BiFunctionWithException<K, ProtocolVersion, V> function;
|
||||
private final Map<K, V> cacheModern = new HashMap<>();
|
||||
private final Map<K, V> cacheLegacy = new HashMap<>();
|
||||
|
||||
public V get(K key, ProtocolVersion clientVersion) {
|
||||
try {
|
||||
if (key == null) return null;
|
||||
Map<K, V> cache = clientVersion == null || clientVersion.getMinorVersion() >= 16 ? cacheModern : cacheLegacy;
|
||||
if (cache.containsKey(key)) return cache.get(key);
|
||||
V value = function.apply(key, clientVersion);
|
||||
if (cache.size() > cacheSize) cache.clear();
|
||||
cache.put(key, value);
|
||||
return value;
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.api.util;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Class with utility methods used in the plugin
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Preconditions {
|
||||
|
||||
/**
|
||||
* Checks length of String and throws {@code IllegalArgumentException} if it is
|
||||
* longer than limit.
|
||||
*
|
||||
* @param string
|
||||
* String to check
|
||||
* @param maxLength
|
||||
* Maximum length of the string
|
||||
* @param name
|
||||
* Name of the variable to use in exception message
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code string} is {@code null} or longer than {@code maxLength} characters
|
||||
*/
|
||||
public static void checkMaxLength(@NonNull String string, int maxLength, String name) {
|
||||
if (string.length() > maxLength) throw new IllegalArgumentException(name + " is longer than " + maxLength + " characters (" + string.length() + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks number range and throws {@code IllegalArgumentException} if number
|
||||
* is out of range.
|
||||
*
|
||||
* @param number
|
||||
* Number to check
|
||||
* @param min
|
||||
* Minimum allowed value
|
||||
* @param max
|
||||
* Maximum allowed value
|
||||
* @param variable
|
||||
* Variable name to use in exception message
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code number} is out of range ({@code number < min || number > max})
|
||||
*/
|
||||
public static void checkRange(Number number, Number min, Number max, String variable) {
|
||||
if (number.doubleValue() < min.doubleValue() || number.doubleValue() > max.doubleValue())
|
||||
throw new IllegalArgumentException(variable + " index out of range (" + min + " - " + max + "), was " + number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if player is loaded and throws {@code IllegalStateException} if not.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @throws IllegalStateException
|
||||
* If player is not loaded
|
||||
*/
|
||||
public static void checkLoaded(TabPlayer player) {
|
||||
if (!player.isLoaded()) throw new IllegalStateException("Player is not loaded yet. Try again later.");
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package me.neznamy.tab.api.util;
|
||||
|
||||
/**
|
||||
* Utility class storing methods working with reflection
|
||||
*/
|
||||
public class ReflectionUtils {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if class with given full name exists,
|
||||
* {@code false} if not.
|
||||
*
|
||||
* @param path
|
||||
* Full class path and name
|
||||
* @return {@code true} if exists, {@code false} if not
|
||||
*/
|
||||
public static boolean classExists(String path) {
|
||||
try {
|
||||
Class.forName(path);
|
||||
return true;
|
||||
} catch (ClassNotFoundException | NullPointerException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package me.neznamy.tab.api.util;
|
||||
|
||||
/**
|
||||
* Supplier that can throw an exception
|
||||
*
|
||||
* @param <T>
|
||||
* Return class type
|
||||
*/
|
||||
public interface SupplierWithException<T> {
|
||||
|
||||
T get() throws Exception;
|
||||
}
|
||||
@@ -3,15 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.plugin.shadow)
|
||||
implementation(libs.plugin.lombok)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(files(libs::class.java.protectionDomain.codeSource.location))
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
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")
|
||||
}
|
||||
@@ -2,15 +2,8 @@ rootProject.name = "build-logic"
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
maven("https://maven.fabricmc.net/")
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
// You're a great IDE IntelliJ, but sometimes you really annoy me. >:(
|
||||
// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
|
||||
versionCatalogs {
|
||||
register("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,12 @@ plugins {
|
||||
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()
|
||||
"website" to rootProject.ext.get("website")!!.toString(),
|
||||
"author" to rootProject.ext.get("author")!!.toString()
|
||||
))
|
||||
}
|
||||
javadoc {
|
||||
@@ -29,6 +31,6 @@ tasks {
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(17))
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ tasks {
|
||||
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.yamlassist")
|
||||
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.me.neznamy.yamlassist")
|
||||
relocate("org.yaml.snakeyaml", "me.neznamy.tab.libs.org.yaml.snakeyaml")
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "3.3.1"
|
||||
version = "4.1.4"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -15,10 +15,11 @@ allprojects {
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bungeecord,
|
||||
projects.krypton,
|
||||
projects.velocity,
|
||||
projects.sponge7,
|
||||
projects.sponge8
|
||||
projects.sponge8,
|
||||
projects.fabric,
|
||||
projects.fabric.v1144
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
|
||||
+15
-5
@@ -1,8 +1,18 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation(libs.bstats.bukkit)
|
||||
compileOnly(libs.bukkit)
|
||||
compileOnly(libs.placeholderapi)
|
||||
compileOnly(libs.vault)
|
||||
compileOnly(libs.authlib)
|
||||
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")
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
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;
|
||||
@@ -9,64 +12,60 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The core for bukkit forwarding events into all enabled features
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitEventListener implements Listener {
|
||||
public class BukkitEventListener implements EventListener<Player>, Listener {
|
||||
|
||||
/** Platform instance */
|
||||
private final BukkitPlatform platform;
|
||||
|
||||
/**
|
||||
* Listener to PlayerQuitEvent to remove player data and forward the event to features
|
||||
* Listens to player quit event.
|
||||
*
|
||||
* @param e
|
||||
* quit event
|
||||
* Disconnect event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e) {
|
||||
if (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onQuit(TabAPI.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 (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
TabAPI.getInstance().getThreadManager().runTask(() ->
|
||||
TabAPI.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerChangedWorldEvent to forward the event to features
|
||||
* Listens to player join event.
|
||||
*
|
||||
* @param e
|
||||
* world changed event
|
||||
* Join event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
join(e.getPlayer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to world change event.
|
||||
*
|
||||
* @param e
|
||||
* World change event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
if (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
TabAPI.getInstance().getThreadManager().runTask(() ->
|
||||
TabAPI.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
|
||||
worldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to PlayerChangedWorldEvent to forward the event to features
|
||||
* Listens to command event to potentially cancel it.
|
||||
*
|
||||
* @param e
|
||||
* command preprocess event
|
||||
* Command event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onCommand(PlayerCommandPreprocessEvent e) {
|
||||
if (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
if (TabAPI.getInstance().getFeatureManager().onCommand(
|
||||
TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
|
||||
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,98 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.protocol.*;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityDestroy;
|
||||
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityMetadata;
|
||||
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityTeleport;
|
||||
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutSpawnEntityLiving;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BukkitPacketBuilder extends PacketBuilder {
|
||||
|
||||
{
|
||||
buildMap.put(PacketPlayOutEntityMetadata.class, (packet, version) -> PacketPlayOutEntityMetadataStorage.build((PacketPlayOutEntityMetadata) packet));
|
||||
buildMap.put(PacketPlayOutEntityTeleport.class, (packet, version) -> PacketPlayOutEntityTeleportStorage.build((PacketPlayOutEntityTeleport) packet));
|
||||
buildMap.put(PacketPlayOutEntityDestroy.class, (packet, version) -> PacketPlayOutEntityDestroyStorage.build((PacketPlayOutEntityDestroy) packet));
|
||||
buildMap.put(PacketPlayOutSpawnEntityLiving.class, (packet, version) -> PacketPlayOutSpawnEntityLivingStorage.build((PacketPlayOutSpawnEntityLiving) packet));
|
||||
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> PacketPlayOutPlayerListHeaderFooterStorage.build((PacketPlayOutPlayerListHeaderFooter) packet, version));
|
||||
buildMap.put(PacketPlayOutChat.class, (packet, version) -> PacketPlayOutChatStorage.build((PacketPlayOutChat) packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> PacketPlayOutScoreboardObjectiveStorage.build((PacketPlayOutScoreboardObjective) packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> PacketPlayOutScoreboardDisplayObjectiveStorage.build((PacketPlayOutScoreboardDisplayObjective) packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> PacketPlayOutScoreboardTeamStorage.build((PacketPlayOutScoreboardTeam) packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> PacketPlayOutScoreboardScoreStorage.build((PacketPlayOutScoreboardScore) packet, version));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 9 || clientVersion.getMinorVersion() >= 9) {
|
||||
//1.9+ server or client, handled by bukkit api or ViaVersion
|
||||
return packet;
|
||||
}
|
||||
//<1.9 client and server
|
||||
return buildBossPacketEntity(packet, clientVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return PacketPlayOutPlayerInfoStorage.build(packet, clientVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return PacketPlayOutPlayerInfoStorage.read(nmsPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket) throws ReflectiveOperationException {
|
||||
return PacketPlayOutScoreboardObjectiveStorage.read(nmsPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket) throws ReflectiveOperationException {
|
||||
return PacketPlayOutScoreboardDisplayObjectiveStorage.read(nmsPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity packet representing requested BossBar packet using Wither on 1.8- clients.
|
||||
*
|
||||
* @param packet
|
||||
* packet to build
|
||||
* @param clientVersion
|
||||
* client version
|
||||
* @return entity BossBar packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (packet.getAction() == Action.UPDATE_STYLE) return null; //nothing to do here
|
||||
|
||||
int entityId = packet.getId().hashCode();
|
||||
if (packet.getAction() == Action.REMOVE) {
|
||||
return build(new PacketPlayOutEntityDestroy(entityId), clientVersion);
|
||||
}
|
||||
DataWatcher w = new DataWatcher();
|
||||
if (packet.getAction() == Action.UPDATE_PCT || packet.getAction() == Action.ADD) {
|
||||
float health = 300*packet.getPct();
|
||||
if (health == 0) health = 1;
|
||||
w.getHelper().setHealth(health);
|
||||
}
|
||||
if (packet.getAction() == Action.UPDATE_NAME || packet.getAction() == Action.ADD) {
|
||||
w.getHelper().setCustomName(packet.getName(), clientVersion);
|
||||
}
|
||||
if (packet.getAction() == Action.ADD) {
|
||||
w.getHelper().setEntityFlags((byte) 32);
|
||||
w.getHelper().setWitherInvulnerableTime(880); // Magic number
|
||||
return build(new PacketPlayOutSpawnEntityLiving(entityId, new UUID(0, 0), EntityType.WITHER, 0, 0, 0, 0, 0, w), clientVersion);
|
||||
} else {
|
||||
return build(new PacketPlayOutEntityMetadata(entityId, w), clientVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,25 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardDisplayObjectiveStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardObjectiveStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Pipeline injection for bukkit
|
||||
* Pipeline injection for Bukkit 1.8+.
|
||||
*/
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/** NMS data storage */
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
|
||||
@Getter private final Function<TabPlayer, ChannelDuplexHandler> channelFunction = BukkitChannelDuplexHandler::new;
|
||||
/** Function for getting player's channel */
|
||||
private static FunctionWithException<TabPlayer, Channel> getChannel;
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
@@ -40,117 +28,48 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Channel getChannel(TabPlayer player) {
|
||||
final BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
|
||||
/**
|
||||
* Attempts to load required classes, fields and methods and marks class as available.
|
||||
* If something fails, error message is printed and class is not marked as available.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
|
||||
} catch (final IllegalAccessException exception) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
|
||||
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");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom channel duplex handler override
|
||||
* Returns {@code true} if pipeline injection is available, {@code false} if not.
|
||||
*
|
||||
* @return {@code true} if pipeline injection is available, {@code false} if not
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
|
||||
public static boolean isAvailable() {
|
||||
return getChannel != null;
|
||||
}
|
||||
|
||||
/** Injected player */
|
||||
private final TabPlayer player;
|
||||
|
||||
@Override
|
||||
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object packet) {
|
||||
try {
|
||||
if (TAB.getInstance().getFeatureManager().onPacketReceive(player, packet)) return;
|
||||
super.channelRead(context, packet);
|
||||
} catch (Throwable e) {
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
|
||||
try {
|
||||
if (PacketPlayOutPlayerInfoStorage.CLASS.isInstance(packet) ||
|
||||
(PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null &&
|
||||
PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket.isInstance(packet))) {
|
||||
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
|
||||
return;
|
||||
}
|
||||
if (antiOverrideTeams && PacketPlayOutScoreboardTeamStorage.CLASS.isInstance(packet)) {
|
||||
long time = System.nanoTime();
|
||||
modifyPlayers(packet);
|
||||
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
|
||||
super.write(context, packet, channelPromise);
|
||||
return;
|
||||
}
|
||||
if (PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
|
||||
}
|
||||
if (PacketPlayOutScoreboardObjectiveStorage.CLASS.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, packet);
|
||||
}
|
||||
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
|
||||
} catch (Throwable e) {
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
|
||||
}
|
||||
try {
|
||||
super.write(context, packet, channelPromise);
|
||||
} catch (Throwable e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to forward packet " + packet.getClass().getSimpleName() + " to " + player.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all real players from team if packet does not come from TAB and reports this to override log
|
||||
*
|
||||
* @param packetPlayOutScoreboardTeam
|
||||
* team packet
|
||||
* @throws ReflectiveOperationException
|
||||
* nmsGameMode
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
|
||||
int action = PacketPlayOutScoreboardTeamStorage.ACTION.getInt(packetPlayOutScoreboardTeam);
|
||||
if (action == 1 || action == 2 || action == 4) return;
|
||||
Collection<String> players = (Collection<String>) PacketPlayOutScoreboardTeamStorage.PLAYERS.get(packetPlayOutScoreboardTeam);
|
||||
String teamName = (String) PacketPlayOutScoreboardTeamStorage.NAME.get(packetPlayOutScoreboardTeam);
|
||||
if (players == null) return;
|
||||
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
|
||||
Collection<String> newList = new ArrayList<>();
|
||||
for (String entry : players) {
|
||||
TabPlayer p = getPlayer(entry);
|
||||
if (p == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(sorting.getShortTeamName(p))) {
|
||||
logTeamOverride(teamName, p.getName(), sorting.getShortTeamName(p));
|
||||
} else {
|
||||
newList.add(entry);
|
||||
}
|
||||
}
|
||||
PacketPlayOutScoreboardTeamStorage.PLAYERS.set(packetPlayOutScoreboardTeam, newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player with matching game profile name. This is different from
|
||||
* real name when a nick plugin changing names of players is used. If no
|
||||
* player was found, returns {@code null}.
|
||||
*
|
||||
* @param name
|
||||
* Game profile name
|
||||
* @return Player with matching game profile name
|
||||
*/
|
||||
private TabPlayer getPlayer(String name) {
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (p.getNickname().equals(name)) return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return getChannel.apply(player);
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Bukkit registry to register bukkit-only and universal placeholders
|
||||
*/
|
||||
public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
|
||||
/** Vault Chat hook */
|
||||
private Chat chat;
|
||||
|
||||
/** NMS server to get TPS from on spigot */
|
||||
private Object server;
|
||||
|
||||
/** TPS field*/
|
||||
private Field recentTps;
|
||||
|
||||
/** Detection for presence of Paper's TPS getter */
|
||||
private Method paperTps;
|
||||
|
||||
/** Detection for presence of Paper's MSPT getter */
|
||||
private Method paperMspt;
|
||||
|
||||
/** Detection for presence of Purpur's AFK getter */
|
||||
private Method purpurIsAfk;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads hooks
|
||||
*/
|
||||
public BukkitPlaceholderRegistry() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VAULT)) {
|
||||
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
|
||||
if (rspChat != null) chat = rspChat.getProvider();
|
||||
}
|
||||
try {
|
||||
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
recentTps = server.getClass().getField("recentTps");
|
||||
} catch (ReflectiveOperationException e) {
|
||||
//not spigot
|
||||
}
|
||||
try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {}
|
||||
try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {}
|
||||
try { purpurIsAfk = Player.class.getMethod("isAfk"); } catch (NoSuchMethodException ignored) {}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void registerPlaceholders(PlaceholderManager manager) {
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
|
||||
if (paperTps != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> formatTPS(Bukkit.getTPS()[0]));
|
||||
} else if (recentTps != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> {
|
||||
try {
|
||||
return formatTPS(((double[]) recentTps.get(server))[0]);
|
||||
} catch (IllegalAccessException e) {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, -1, () -> -1);
|
||||
}
|
||||
if (paperMspt != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> format(Bukkit.getAverageTickTime()));
|
||||
}
|
||||
Plugin essentials = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.ESSENTIALS);
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.AFK, 500, p -> {
|
||||
if (essentials != null) {
|
||||
try {
|
||||
Object user = essentials.getClass().getMethod("getUser", UUID.class).invoke(essentials, p.getUniqueId());
|
||||
if ((boolean) user.getClass().getMethod("isAfk").invoke(user)) return true;
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get AFK status of " + p.getName() + " using Essentials", e);
|
||||
}
|
||||
}
|
||||
return purpurIsAfk != null && ((Player)p.getPlayer()).isAfk();
|
||||
});
|
||||
// Removed placeholder, keeping the implementation to avoid placeholder breaking for users on update
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.ESSENTIALS_NICK, -1, TabPlayer::getName);
|
||||
|
||||
if (chat != null) {
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.VAULT_PREFIX, 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.VAULT_SUFFIX, 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
|
||||
} else {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.VAULT_PREFIX, -1, () -> "");
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.VAULT_SUFFIX, -1, () -> "");
|
||||
}
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
|
||||
super.registerPlaceholders(manager);
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.api.util.ReflectionUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PetFix;
|
||||
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.features.unlimitedtags.BukkitNameTagX;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.permission.Vault;
|
||||
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.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
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.placeholders.PlayerPlaceholderImpl;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Implementation of Platform interface for Bukkit platform
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitPlatform extends BackendPlatform {
|
||||
|
||||
@Getter private final BukkitPacketBuilder packetBuilder = new BukkitPacketBuilder();
|
||||
@Getter private final BukkitPipelineInjector pipelineInjector = NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
|
||||
|
||||
/** Plugin instance for registering tasks and events */
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
/** Variables checking presence of other plugins to hook into */
|
||||
private final boolean placeholderAPI = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PLACEHOLDER_API);
|
||||
@Getter @Setter private boolean libsDisguisesEnabled = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LIBS_DISGUISES);
|
||||
private final boolean viaVersion = ReflectionUtils.classExists("com.viaversion.viaversion.api.Via");
|
||||
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PROTOCOL_SUPPORT);
|
||||
|
||||
@Override
|
||||
public PermissionPlugin detectPermissionPlugin() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LUCKPERMS)) {
|
||||
return new LuckPerms(getPluginVersion(TabConstants.Plugin.LUCKPERMS));
|
||||
} else if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VAULT)) {
|
||||
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
|
||||
if (provider == null) return new None();
|
||||
return new Vault(provider.getProvider(), getPluginVersion(TabConstants.Plugin.VAULT));
|
||||
} else {
|
||||
return new None();
|
||||
}
|
||||
}
|
||||
|
||||
public BossBarManagerImpl getLegacyBossBar() {
|
||||
return new WitherBossBar(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPluginVersion(String plugin) {
|
||||
Plugin pl = Bukkit.getPluginManager().getPlugin(plugin);
|
||||
return pl == null ? null : pl.getDescription().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (Player p : getOnlinePlayers()) {
|
||||
TAB.getInstance().addPlayer(new BukkitTabPlayer(p, getProtocolVersion(p)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerPlaceholders() {
|
||||
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
public NameTag getUnlimitedNametags() {
|
||||
return new BukkitNameTagX(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TabExpansion getTabExpansion() {
|
||||
if (placeholderAPI) {
|
||||
BukkitTabExpansion expansion = new BukkitTabExpansion();
|
||||
expansion.register();
|
||||
return expansion;
|
||||
}
|
||||
return new EmptyTabExpansion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabFeature getPetFix() {
|
||||
return new PetFix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TabFeature getPerWorldPlayerlist() {
|
||||
return new PerWorldPlayerList(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API
|
||||
*
|
||||
* @return online players from Bukkit API
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Player[] getOnlinePlayers() {
|
||||
try {
|
||||
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
|
||||
if (players instanceof Player[]) {
|
||||
//1.7-
|
||||
return (Player[]) players;
|
||||
} else {
|
||||
//1.8+
|
||||
return ((Collection<Player>)players).toArray(new Player[0]);
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get online players", e);
|
||||
return new Player[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(String identifier) {
|
||||
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
|
||||
if (identifier.startsWith("%rel_")) {
|
||||
//relational placeholder
|
||||
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRelationalRefresh(identifier), (viewer, target) ->
|
||||
placeholderAPI ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
|
||||
} else {
|
||||
//normal placeholder
|
||||
if (identifier.startsWith("%sync:")) {
|
||||
int refresh = pl.getServerPlaceholderRefreshIntervals().getOrDefault(identifier,
|
||||
pl.getPlayerPlaceholderRefreshIntervals().getOrDefault(identifier, pl.getDefaultRefresh()));
|
||||
String syncedPlaceholder = "%" + identifier.substring(6, identifier.length()-1) + "%";
|
||||
pl.registerPlaceholder(new PlayerPlaceholderImpl(identifier, refresh, null) {
|
||||
|
||||
@Override
|
||||
public Object request(TabPlayer p) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
long time = System.nanoTime();
|
||||
updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(getIdentifier(), System.nanoTime()-time);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (pl.getServerPlaceholderRefreshIntervals().containsKey(identifier)) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, pl.getServerPlaceholderRefreshIntervals().get(identifier), () ->
|
||||
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
|
||||
} else {
|
||||
int refresh = pl.getPlayerPlaceholderRefreshIntervals().getOrDefault(identifier, pl.getDefaultRefresh());
|
||||
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
|
||||
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets protocol version of requested player and returns it.
|
||||
*
|
||||
* @param player
|
||||
* Player to get protocol version of
|
||||
* @return protocol version of the player
|
||||
*/
|
||||
public int getProtocolVersion(Player player) {
|
||||
if (protocolSupport) {
|
||||
int version = getProtocolVersionPS(player);
|
||||
//some PS versions return -1 on unsupported server versions instead of throwing exception
|
||||
if (version != -1 && version < TAB.getInstance().getServerVersion().getNetworkId()) return version;
|
||||
}
|
||||
if (viaVersion) {
|
||||
return ProtocolVersion.getPlayerVersionVia(player.getUniqueId(), player.getName());
|
||||
}
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns protocol version of requested player using ProtocolSupport
|
||||
*
|
||||
* @param player
|
||||
* Player to get protocol version of
|
||||
* @return protocol version of the player using ProtocolSupport
|
||||
*/
|
||||
private int getProtocolVersionPS(Player player) {
|
||||
try {
|
||||
Object protocolVersion = Class.forName("protocolsupport.api.ProtocolSupportAPI").getMethod("getProtocolVersion", Player.class).invoke(null, player);
|
||||
int version = (int) protocolVersion.getClass().getMethod("getId").invoke(protocolVersion);
|
||||
TAB.getInstance().debug("ProtocolSupport returned protocol version " + version + " for " + player.getName() + " (online=" + player.isOnline() + ")");
|
||||
return version;
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError(String.format("Failed to get protocol version of %s using ProtocolSupport", player.getName()), e);
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends console message using ConsoleCommandSender, due to
|
||||
* Paper not translating colors correctly in Logger messages
|
||||
* and to allow RGB (at least on Paper, doesn't work on spigot)
|
||||
*
|
||||
* @param message
|
||||
* Message to send
|
||||
* @param translateColors
|
||||
* Whether color codes should be translated or not
|
||||
*/
|
||||
@Override
|
||||
public void sendConsoleMessage(String message, boolean translateColors) {
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + (translateColors ?
|
||||
EnumChatFormat.color(RGBUtils.getInstance().convertToBukkitFormat(message,
|
||||
TAB.getInstance().getServerVersion().getMinorVersion() >= 16))
|
||||
: message));
|
||||
}
|
||||
}
|
||||
@@ -1,143 +1,24 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.util.SupplierWithException;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.*;
|
||||
import org.bukkit.entity.Player;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Main class for Bukkit platform
|
||||
* Main class for Bukkit.
|
||||
*/
|
||||
public class BukkitTAB extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
String version = Bukkit.getBukkitVersion().split("-")[0];
|
||||
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Server version: " + version + " (" + serverPackage + ")"));
|
||||
if (!isVersionSupported()) {
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
BukkitPlatform platform = new BukkitPlatform(this);
|
||||
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(version), version + " (" + serverPackage + ")", getDataFolder(), getLogger()));
|
||||
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
|
||||
}
|
||||
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(platform), this);
|
||||
TAB.getInstance().load();
|
||||
Metrics metrics = new Metrics(this, 5304);
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PLACEHOLDER_API, () -> Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PLACEHOLDER_API) ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPlugin().getName()));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION, () -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
|
||||
PluginCommand cmd = Bukkit.getPluginCommand("tab");
|
||||
if (cmd == null) return;
|
||||
TABCommand command = new TABCommand();
|
||||
cmd.setExecutor(command);
|
||||
cmd.setTabCompleter(command);
|
||||
boolean folia = ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer");
|
||||
TAB.create(folia ? new FoliaPlatform(this) : new BukkitPlatform(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
//null check due to compatibility check making instance not get set on unsupported versions
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all used NMS classes, constructors, fields and methods.
|
||||
* Returns {@code true} if everything went successfully and version is marked as compatible,
|
||||
* {@code false} if anything went wrong or version is not marked as compatible.
|
||||
*
|
||||
* @return {@code true} if server version is compatible, {@code false} if not
|
||||
*/
|
||||
private boolean isVersionSupported() {
|
||||
List<String> supportedVersions = Arrays.asList(
|
||||
"v1_5_R1", "v1_5_R2", "v1_5_R3", "v1_6_R1", "v1_6_R2", "v1_6_R3",
|
||||
"v1_7_R1", "v1_7_R2", "v1_7_R3", "v1_7_R4", "v1_8_R1", "v1_8_R2", "v1_8_R3",
|
||||
"v1_9_R1", "v1_9_R2", "v1_10_R1", "v1_11_R1", "v1_12_R1", "v1_13_R1", "v1_13_R2",
|
||||
"v1_14_R1", "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1", "v1_18_R1",
|
||||
"v1_18_R2", "v1_19_R1", "v1_19_R2", "v1_19_R3");
|
||||
String supportedVersionRange = "1.5 - 1.19.4";
|
||||
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
try {
|
||||
long time = System.currentTimeMillis();
|
||||
NMSStorage.setInstance(getNMSLoader());
|
||||
if (supportedVersions.contains(serverPackage)) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
|
||||
return true;
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on " + supportedVersionRange + ". Disabling just to stay safe."));
|
||||
}
|
||||
} catch (IllegalStateException ex) {
|
||||
if (supportedVersions.contains(serverPackage)) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is marked as compatible, but a compatibility issue was found. Please report this issue (include your server version & fork too)"));
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports " + supportedVersionRange + ". Disabling."));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private NMSStorage getNMSLoader() {
|
||||
List<SupplierWithException<NMSStorage>> loaders = new ArrayList<>();
|
||||
loaders.add(BukkitLegacyNMSStorage::new);
|
||||
loaders.add(BukkitModernNMSStorage::new);
|
||||
loaders.add(MojangModernNMSStorage::new);
|
||||
loaders.add(ThermosNMSStorage::new);
|
||||
for (SupplierWithException<NMSStorage> loader : loaders) {
|
||||
try {
|
||||
return loader.get();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
throw new IllegalStateException("Unsupported server version");
|
||||
}
|
||||
|
||||
/**
|
||||
* Command handler for /tab command
|
||||
*/
|
||||
private static class TABCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
|
||||
if (TabAPI.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
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof Player) {
|
||||
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
|
||||
if (p == null) return new ArrayList<>(); //player not loaded correctly
|
||||
}
|
||||
return TAB.getInstance().getCommand().complete(p, args);
|
||||
}
|
||||
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,236 +1,70 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossFlag;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
|
||||
import me.neznamy.tab.api.protocol.Skin;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.ITabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.boss.BarColor;
|
||||
import org.bukkit.boss.BarFlag;
|
||||
import org.bukkit.boss.BarStyle;
|
||||
import org.bukkit.boss.BossBar;
|
||||
import org.bukkit.entity.Entity;
|
||||
import 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.metadata.MetadataValue;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for Bukkit platform
|
||||
*/
|
||||
public class BukkitTabPlayer extends ITabPlayer {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Getter
|
||||
public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
/** Player's NMS handle (EntityPlayer), preloading for speed */
|
||||
private Object handle;
|
||||
@NotNull
|
||||
private final Scoreboard<BukkitTabPlayer, ?> scoreboard = ScoreboardLoader.getInstance().apply(this);
|
||||
|
||||
/** Player's connection for sending packets, preloading for speed */
|
||||
@Getter private Object playerConnection;
|
||||
|
||||
/** Bukkit BossBars the player can currently see */
|
||||
private final Map<UUID, BossBar> bossBars = new HashMap<>();
|
||||
@NotNull
|
||||
private final TabListBase<?> tabList = TabListBase.getInstance().apply(this);
|
||||
|
||||
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
|
||||
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
|
||||
@NotNull
|
||||
private final BossBar bossBar = BossBarLoader.findInstance(this);
|
||||
|
||||
@NotNull
|
||||
private final EntityView entityView = PacketEntityView.isAvailable() ? new PacketEntityView(this) : new DummyEntityView();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given bukkit player and protocol version
|
||||
* Constructs new instance with given bukkit player
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param p
|
||||
* bukkit player
|
||||
* @param protocolVersion
|
||||
* Player's protocol network id
|
||||
*/
|
||||
public BukkitTabPlayer(Player p, int protocolVersion) {
|
||||
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(), p.getWorld().getName(), protocolVersion, true);
|
||||
try {
|
||||
handle = NMSStorage.getInstance().getHandle.invoke(player);
|
||||
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get playerConnection of " + p.getName(), e);
|
||||
}
|
||||
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
|
||||
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName(), platform.getServerVersion().getNetworkId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NonNull String permission) {
|
||||
long time = System.nanoTime();
|
||||
boolean value = getPlayer().hasPermission(permission);
|
||||
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
|
||||
return value;
|
||||
public boolean hasPermission(@NotNull String permission) {
|
||||
return getPlayer().hasPermission(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
try {
|
||||
int ping = NMSStorage.getInstance().PING.getInt(handle);
|
||||
if (ping > 10000 || ping < 0) ping = -1;
|
||||
return ping;
|
||||
} catch (IllegalAccessException e) {
|
||||
return -1;
|
||||
}
|
||||
return PingRetriever.getPing(getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Object nmsPacket) {
|
||||
if (nmsPacket == null || !getPlayer().isOnline()) return;
|
||||
long time = System.nanoTime();
|
||||
try {
|
||||
if (nmsPacket instanceof PacketPlayOutBoss) {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
|
||||
handle((PacketPlayOutBoss) nmsPacket);
|
||||
} else {
|
||||
handleVia((PacketPlayOutBoss) nmsPacket);
|
||||
}
|
||||
} else if (nmsPacket instanceof PacketPlayOutChat) {
|
||||
getPlayer().sendMessage(((PacketPlayOutChat) nmsPacket).getMessage().toLegacyText());
|
||||
} else {
|
||||
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
|
||||
}
|
||||
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles PacketPlayOutBoss packet send request using Bukkit API,
|
||||
* since the API offers everything we need and makes us not need to
|
||||
* deal with NMS code at all.
|
||||
*
|
||||
* @param packet
|
||||
* packet request to handle using Bukkit API
|
||||
*/
|
||||
private void handle(PacketPlayOutBoss packet) {
|
||||
BossBar bar = bossBars.get(packet.getId());
|
||||
if (packet.getAction() == PacketPlayOutBoss.Action.ADD) {
|
||||
if (bossBars.containsKey(packet.getId())) return;
|
||||
bar = Bukkit.createBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
|
||||
BarColor.valueOf(packet.getColor().name()),
|
||||
BarStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
if (packet.isCreateWorldFog()) bar.addFlag(BarFlag.CREATE_FOG);
|
||||
if (packet.isDarkenScreen()) bar.addFlag(BarFlag.DARKEN_SKY);
|
||||
if (packet.isPlayMusic()) bar.addFlag(BarFlag.PLAY_BOSS_MUSIC);
|
||||
bar.setProgress(packet.getPct());
|
||||
bossBars.put(packet.getId(), bar);
|
||||
bar.addPlayer(getPlayer());
|
||||
return;
|
||||
}
|
||||
if (bar == null) return; //no idea how
|
||||
switch (packet.getAction()) {
|
||||
case REMOVE:
|
||||
bar.removePlayer(getPlayer());
|
||||
bossBars.remove(packet.getId());
|
||||
break;
|
||||
case UPDATE_PCT:
|
||||
bar.setProgress(packet.getPct());
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
bar.setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
bar.setColor(BarColor.valueOf(packet.getColor().name()));
|
||||
bar.setStyle(BarStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
bar = bossBars.get(packet.getId());
|
||||
processFlag(bar, packet.isCreateWorldFog(), BarFlag.CREATE_FOG);
|
||||
processFlag(bar, packet.isDarkenScreen(), BarFlag.DARKEN_SKY);
|
||||
processFlag(bar, packet.isPlayMusic(), BarFlag.PLAY_BOSS_MUSIC);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles PacketPlayOutBoss packet request for 1.9+ clients on
|
||||
* 1.8 servers using ViaVersion API instead of using Wither.
|
||||
*
|
||||
* @param packet
|
||||
* packet request to handle using ViaVersion API
|
||||
*/
|
||||
private void handleVia(PacketPlayOutBoss packet) {
|
||||
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar;
|
||||
switch (packet.getAction()) {
|
||||
case ADD:
|
||||
if (viaBossBars.containsKey(packet.getId())) return;
|
||||
bar = Via.getAPI().legacyAPI().createLegacyBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16),
|
||||
packet.getPct(),
|
||||
BossColor.valueOf(packet.getColor().name()),
|
||||
BossStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
//fog missing from via API
|
||||
if (packet.isDarkenScreen()) bar.addFlag(BossFlag.DARKEN_SKY);
|
||||
if (packet.isPlayMusic()) bar.addFlag(BossFlag.PLAY_BOSS_MUSIC);
|
||||
viaBossBars.put(packet.getId(), bar);
|
||||
bar.addPlayer(getPlayer().getUniqueId());
|
||||
break;
|
||||
case REMOVE:
|
||||
viaBossBars.get(packet.getId()).removePlayer(getPlayer().getUniqueId());
|
||||
viaBossBars.remove(packet.getId());
|
||||
break;
|
||||
case UPDATE_PCT:
|
||||
viaBossBars.get(packet.getId()).setHealth(packet.getPct());
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
viaBossBars.get(packet.getId()).setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16));
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
viaBossBars.get(packet.getId()).setColor(BossColor.valueOf(packet.getColor().name()));
|
||||
viaBossBars.get(packet.getId()).setStyle(BossStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
bar = viaBossBars.get(packet.getId());
|
||||
//fog missing from via API
|
||||
processFlagVia(bar, packet.isDarkenScreen(), BossFlag.DARKEN_SKY);
|
||||
processFlagVia(bar, packet.isPlayMusic(), BossFlag.PLAY_BOSS_MUSIC);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets BossBar flag to requested target value.
|
||||
*
|
||||
* @param bar
|
||||
* BossBar to set flag of
|
||||
* @param targetValue
|
||||
* Target value of the flag
|
||||
* @param flag
|
||||
* Flag to set value of
|
||||
*/
|
||||
private void processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
|
||||
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
|
||||
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets BossBar flag to requested target value.
|
||||
*
|
||||
* @param bar
|
||||
* BossBar to set flag of
|
||||
* @param targetValue
|
||||
* Target value of the flag
|
||||
* @param flag
|
||||
* Flag to set value of
|
||||
*/
|
||||
private void processFlagVia(com.viaversion.viaversion.api.legacy.bossbar.BossBar bar, boolean targetValue, BossFlag flag) {
|
||||
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
|
||||
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendMessage(getPlatform().toBukkitFormat(message, getVersion().supportsRGB()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -240,48 +74,47 @@ public class BukkitTabPlayer extends ITabPlayer {
|
||||
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
try {
|
||||
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).isLibsDisguisesEnabled()) return false;
|
||||
return (boolean) Class.forName("me.libraryaddict.disguise.DisguiseAPI").getMethod("isDisguised", Entity.class).invoke(null, getPlayer());
|
||||
} catch (LinkageError | ReflectiveOperationException e) {
|
||||
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
|
||||
TAB.getInstance().getErrorManager().printError("Failed to check disguise status using LibsDisguises", e);
|
||||
((BukkitPlatform)TAB.getInstance().getPlatform()).setLibsDisguisesEnabled(false);
|
||||
return false;
|
||||
}
|
||||
return LibsDisguisesHook.isDisguised(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Skin getSkin() {
|
||||
try {
|
||||
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get("textures");
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
return new Skin(property.getValue(), property.getSignature());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get skin of " + getName(), e);
|
||||
return null;
|
||||
}
|
||||
@Nullable
|
||||
public TabList.Skin getSkin() {
|
||||
return tabList.getSkin();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
return (Player) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer().isOnline();
|
||||
public BukkitPlatform getPlatform() {
|
||||
return (BukkitPlatform) platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
|
||||
for (MetadataValue v : getPlayer().getMetadata("vanished")) {
|
||||
if (v.asBoolean()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().getGameMode().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealth() {
|
||||
return getPlayer().getHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return getPlayer().getDisplayName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Class containing static util methods used on Bukkit.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class BukkitUtils {
|
||||
|
||||
/** Whether compatibility exceptions should be printed or not, enabling when adding support for new versions */
|
||||
public static final boolean PRINT_EXCEPTIONS = false;
|
||||
|
||||
private static boolean compatibilityIssue;
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API. This requires reflection, as return type changed in 1.8,
|
||||
* and we want to avoid errors.
|
||||
*
|
||||
* @return Online players from Bukkit API.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public static Player[] getOnlinePlayers() {
|
||||
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
|
||||
if (players instanceof Player[]) {
|
||||
//1.7-
|
||||
return (Player[]) players;
|
||||
} else {
|
||||
//1.8+
|
||||
return ((Collection<Player>)players).toArray(new Player[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a console warn that some compatibility issue was found.
|
||||
*
|
||||
* @param exception
|
||||
* Exception thrown when initializing fields, if applicable
|
||||
* @param failedCheck
|
||||
* Check that failed
|
||||
* @param fallback
|
||||
* Fallback implementation, if available
|
||||
* @param missingFeatures
|
||||
* Features that will be broken because of the incompatibility
|
||||
*/
|
||||
public static void compatibilityError(@NotNull Exception exception, @NotNull String failedCheck,
|
||||
@Nullable String fallback, @NotNull String... missingFeatures) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(EnumChatFormat.RED);
|
||||
sb.append("[TAB] Failed to initialize minecraft fields for ");
|
||||
sb.append(failedCheck);
|
||||
sb.append(" due to a compatibility error. ");
|
||||
if (fallback != null) {
|
||||
sb.append("Using fallback solution using ").append(fallback).append(". ");
|
||||
} else {
|
||||
sb.append("No fallback solution was found. ");
|
||||
}
|
||||
if (missingFeatures.length > 0) sb.append("This will result in: ");
|
||||
for (int i=0; i<missingFeatures.length; i++) {
|
||||
sb.append("\n").append("#").append(i + 1).append(": ").append(missingFeatures[i]);
|
||||
}
|
||||
Bukkit.getConsoleSender().sendMessage(sb.toString());
|
||||
if (PRINT_EXCEPTIONS) exception.printStackTrace();
|
||||
compatibilityIssue = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message asking user to update the plugin if some compatibility issue was found.
|
||||
*/
|
||||
public static void sendCompatibilityMessage() {
|
||||
if (!compatibilityIssue) return;
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] Please update the plugin to " +
|
||||
"a version with native support for your server version for optimal experience. This plugin version " +
|
||||
"was made for " + ProtocolVersion.V1_5.getFriendlyName() + " - " + ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName() + ".");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Class for finding BossBar implementation based on server and client version.
|
||||
*/
|
||||
public class BossBarLoader {
|
||||
|
||||
/**
|
||||
* Finds best available instance for given player.
|
||||
*
|
||||
* @param player
|
||||
* Player to find instance for
|
||||
* @return BossBar instance for player
|
||||
*/
|
||||
@NotNull
|
||||
public static BossBar findInstance(@NotNull BukkitTabPlayer player) {
|
||||
// 1.9+ server, handle using API, potential 1.8 players are handled by ViaVersion
|
||||
if (BukkitReflection.getMinorVersion() >= 9) return new BukkitBossBar(player);
|
||||
|
||||
// 1.9+ player on 1.8 server, handle using ViaVersion API
|
||||
if (player.getVersion().getMinorVersion() >= 9) return new ViaBossBar(player);
|
||||
|
||||
// 1.8- server and player, use wither
|
||||
return new EntityBossBar(player);
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
+80
-17
@@ -3,30 +3,54 @@ 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.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TAB's expansion for PlaceholderAPI
|
||||
*/
|
||||
@Getter
|
||||
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
|
||||
|
||||
/** Map holding all values for all players for easy and high-performance access */
|
||||
private final WeakHashMap<Player, Map<String, String>> values = new WeakHashMap<>();
|
||||
@NotNull
|
||||
private final WeakHashMap<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
|
||||
|
||||
@Getter private final String author = TabConstants.PLUGIN_AUTHOR;
|
||||
@Getter private final String identifier = TabConstants.PLUGIN_ID;
|
||||
@Getter private final String version = TabConstants.PLUGIN_VERSION;
|
||||
/** List of all placeholders offered by the plugin for command suggestions */
|
||||
@NotNull
|
||||
private final List<String> placeholders = Collections.unmodifiableList(Arrays.asList(
|
||||
"%tab_tabprefix%",
|
||||
"%tab_tabsuffix%",
|
||||
"%tab_tagprefix%",
|
||||
"%tab_tagsuffix%",
|
||||
"%tab_customtabname%",
|
||||
"%tab_customtagname%",
|
||||
"%tab_belowname%",
|
||||
"%tab_abovename%",
|
||||
"%tab_tabprefix_raw%",
|
||||
"%tab_tabsuffix_raw%",
|
||||
"%tab_tagprefix_raw%",
|
||||
"%tab_tagsuffix_raw%",
|
||||
"%tab_customtabname_raw%",
|
||||
"%tab_customtagname_raw%",
|
||||
"%tab_belowname_raw%",
|
||||
"%tab_abovename_raw%",
|
||||
"%tab_scoreboard_name%",
|
||||
"%tab_scoreboard_visible%",
|
||||
"%tab_bossbar_visible%",
|
||||
"%tab_nametag_preview%",
|
||||
"%tab_nametag_visibility%",
|
||||
"%tab_replace_<placeholder>%",
|
||||
"%tab_placeholder_<placeholder>%"
|
||||
));
|
||||
|
||||
@Override
|
||||
public boolean persist() {
|
||||
@@ -34,27 +58,66 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onPlaceholderRequest(Player player, @NotNull String identifier) {
|
||||
@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, TabAPI.getInstance().getPlaceholderManager().findReplacement(placeholder,
|
||||
text = text.replace(placeholder, TAB.getInstance().getPlaceholderManager().findReplacement(placeholder,
|
||||
PlaceholderAPI.setPlaceholders(player, placeholder)));
|
||||
}
|
||||
} while (!textBefore.equals(text));
|
||||
return text;
|
||||
}
|
||||
if (identifier.startsWith("placeholder_")) {
|
||||
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", (TabFeature) TabAPI.getInstance().getPlaceholderManager());
|
||||
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring("placeholder_".length()) + "%", TAB.getInstance().getPlaceholderManager());
|
||||
}
|
||||
return values.get(player).get(identifier);
|
||||
if (player == null) return "<Player cannot be null>";
|
||||
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
|
||||
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
|
||||
return values.get(p).get(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(TabPlayer player, String key, String value) {
|
||||
values.computeIfAbsent((Player) player.getPlayer(), p -> new HashMap<>()).put(key, value);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
-23
@@ -5,9 +5,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
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;
|
||||
@@ -17,43 +17,55 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
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")
|
||||
@RequiredArgsConstructor
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
|
||||
|
||||
/** Config options */
|
||||
private final boolean allowBypass = TabAPI.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
private final List<String> ignoredWorlds = TabAPI.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
|
||||
private final Map<String, List<String>> sharedWorlds = TabAPI.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
private final boolean allowBypass = config().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
private final List<String> ignoredWorlds = config().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
|
||||
private final Map<String, List<String>> sharedWorlds = config().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
|
||||
/** Plugin reference*/
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
@Getter private final String featureName = "Per world PlayerList";
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
|
||||
/**
|
||||
* Constructs new instance and registers events
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance to register events
|
||||
*/
|
||||
public PerWorldPlayerList(JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
checkPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
for (Player pl : BukkitUtils.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
}
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join event listener to synchronously perform hiding.
|
||||
*
|
||||
* @param e
|
||||
* Join event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
long time = System.nanoTime();
|
||||
@@ -61,6 +73,12 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
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();
|
||||
@@ -76,8 +94,8 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
* @param p
|
||||
* Player to update
|
||||
*/
|
||||
private void checkPlayer(Player p) {
|
||||
for (Player all : Bukkit.getOnlinePlayers()) {
|
||||
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);
|
||||
@@ -94,7 +112,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
* Target displayed in the TabList
|
||||
* @return {@code true} if viewer should see target, {@code false} if not.
|
||||
*/
|
||||
private boolean shouldSee(Player viewer, Player target) {
|
||||
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
|
||||
@@ -107,4 +125,10 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
}
|
||||
return viewerWorldGroup.equals(targetWorldGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getFeatureName() {
|
||||
return "Per world PlayerList";
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityMetadataStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* 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 extends TabFeature {
|
||||
|
||||
/** NMS Storage reference for quick access */
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
|
||||
/** DataWatcher position of pet owner field */
|
||||
private final int petOwnerPosition = getPetOwnerPosition();
|
||||
|
||||
/** Logger of last interacts to prevent feature not working on 1.16 */
|
||||
private final WeakHashMap<TabPlayer, Long> lastInteractFix = new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
* Since 1.16, client sends interact packet twice for entities affected
|
||||
* by removed owner field. Because of that, we need to cancel the duplicated
|
||||
* packet to avoid double toggle and preventing the entity from getting
|
||||
* its pose changed. The duplicated packet is usually sent instantly in the
|
||||
* same millisecond, however, when installing ProtocolLib with MyPet, the delay
|
||||
* is up to 3 ticks. When holding right-click on an entity, interact is sent every
|
||||
* 200 milliseconds, which is the value we should not go above. Optimal value is
|
||||
* therefore between 150 and 200 milliseconds.
|
||||
*/
|
||||
private static final int INTERACT_COOLDOWN = 160;
|
||||
|
||||
@Getter private final String featureName = "Pet name fix";
|
||||
|
||||
/**
|
||||
* Returns position of pet owner field based on server version
|
||||
*
|
||||
* @return position of pet owner field based on server version
|
||||
*/
|
||||
private int getPetOwnerPosition() {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
//1.17.x, 1.18.x, 1.19.x
|
||||
return 18;
|
||||
} else if (nms.getMinorVersion() >= 15) {
|
||||
//1.15.x, 1.16.x
|
||||
return 17;
|
||||
} else if (nms.getMinorVersion() >= 14) {
|
||||
//1.14.x
|
||||
return 16;
|
||||
} else if (nms.getMinorVersion() >= 10) {
|
||||
//1.10.x - 1.13.x
|
||||
return 14;
|
||||
} else {
|
||||
//1.9.x
|
||||
return 13;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a packet if previous one arrived with no delay to prevent double toggle since 1.16
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
@Override
|
||||
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
|
||||
if (nms.PacketPlayInUseEntity.isInstance(packet) && isInteract(nms.PacketPlayInUseEntity_ACTION.get(packet))) {
|
||||
if (System.currentTimeMillis() - lastInteractFix.getOrDefault(sender, 0L) < INTERACT_COOLDOWN) {
|
||||
//last interact packet was sent right now, cancelling to prevent double-toggle due to this feature enabled
|
||||
return true;
|
||||
}
|
||||
//this is the first packet, saving player so the next packet can be cancelled
|
||||
lastInteractFix.put(sender, System.currentTimeMillis());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided entity use action is INTERACT or not.
|
||||
*
|
||||
* @param action
|
||||
* Action to check
|
||||
* @return {@code true} if action is INTERACT, {@code false} if not.
|
||||
*/
|
||||
private boolean isInteract(Object action) {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
return nms.PacketPlayInUseEntity$d.isInstance(action);
|
||||
} else {
|
||||
return action.toString().equals("INTERACT");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes pet owner field from DataWatcher
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
|
||||
if (PacketPlayOutEntityMetadataStorage.CLASS.isInstance(packet)) {
|
||||
Object removedEntry = null;
|
||||
List<Object> items = (List<Object>) PacketPlayOutEntityMetadataStorage.LIST.get(packet);
|
||||
if (items == null) return;
|
||||
try {
|
||||
for (Object item : items) {
|
||||
if (item == null) continue;
|
||||
int slot;
|
||||
Object value;
|
||||
if (nms.is1_19_3Plus()) {
|
||||
slot = DataWatcher.DataValue_POSITION.getInt(item);
|
||||
value = DataWatcher.DataValue_VALUE.get(item);
|
||||
} else {
|
||||
slot = DataWatcherObject.SLOT.getInt(DataWatcherItem.TYPE.get(item));
|
||||
value = DataWatcherItem.VALUE.get(item);
|
||||
}
|
||||
if (slot == petOwnerPosition) {
|
||||
if (value instanceof Optional || value instanceof com.google.common.base.Optional) {
|
||||
removedEntry = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removedEntry != null) items.remove(removedEntry);
|
||||
} catch (ConcurrentModificationException e) {
|
||||
//no idea how can this list change in another thread since it's created for the packet but whatever, try again
|
||||
onPacketSend(receiver, packet);
|
||||
}
|
||||
} else if (PacketPlayOutSpawnEntityLivingStorage.CLASS.isInstance(packet) && PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER != null) {
|
||||
//<1.15
|
||||
DataWatcher watcher = DataWatcher.fromNMS(PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER.get(packet));
|
||||
DataWatcherItem petOwner = watcher.getItem(petOwnerPosition);
|
||||
if (petOwner != null && (petOwner.getValue() instanceof Optional || petOwner.getValue() instanceof com.google.common.base.Optional)) {
|
||||
watcher.removeValue(petOwnerPosition);
|
||||
PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER.set(packet, watcher.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,64 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
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.protocol.PacketPlayOutEntityTeleport;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import 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 {
|
||||
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
|
||||
|
||||
/** Distance of the Wither in blocks */
|
||||
/** Distance of the wither in blocks */
|
||||
private static final int WITHER_DISTANCE = 60;
|
||||
|
||||
/** Reference to plugin for registering listener */
|
||||
private final JavaPlugin plugin;
|
||||
/**
|
||||
* Constructs new instance and registers events
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance to register events
|
||||
*/
|
||||
public WitherBossBar(JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
|
||||
//when in a small window, it's about 100ms
|
||||
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
|
||||
this, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
getFeatureName(), TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
super.load();
|
||||
teleport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Wither's location for all online players
|
||||
* 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 loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
|
||||
Location eyeLocation = ((BukkitTabPlayer)p).getPlayer().getEyeLocation();
|
||||
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(WITHER_DISTANCE));
|
||||
if (loc.getY() < 1) loc.setY(1);
|
||||
p.sendCustomPacket(new PacketPlayOutEntityTeleport(line.getUniqueId().hashCode(), loc.getX(), loc.getY(), loc.getZ(), 0, 0), TabConstants.PacketCategory.BOSSBAR_WITHER_TELEPORT);
|
||||
((BackendTabPlayer)p).getEntityView().teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,11 +70,24 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning wither as respawn screen destroys all entities in client
|
||||
* Respawning wither as respawn screen destroys all entities in client.
|
||||
*
|
||||
* @param e
|
||||
* Respawn event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(this, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
|
||||
() -> detectBossBarsAndSend(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
|
||||
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);
|
||||
}
|
||||
}
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
|
||||
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
|
||||
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.HandlerList;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The core class for unlimited NameTag mode on Bukkit
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitNameTagX extends BackendNameTagX {
|
||||
|
||||
/** Reference to NMS storage for quick access */
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
|
||||
/** Bukkit event listener */
|
||||
private final EventListener eventListener = new EventListener(this);
|
||||
|
||||
/** Plugin reference */
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
|
||||
super.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
|
||||
if (sender.getVersion().getMinorVersion() == 8 && nms.PacketPlayInUseEntity.isInstance(packet)) {
|
||||
int entityId = nms.PacketPlayInUseEntity_ENTITY.getInt(packet);
|
||||
TabPlayer attacked = null;
|
||||
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
|
||||
if (all.isLoaded() && getArmorStandManager(all).hasArmorStandWithID(entityId)) {
|
||||
attacked = all;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attacked != null && attacked != sender) {
|
||||
nms.PacketPlayInUseEntity_ENTITY.set(packet, getEntityId(attacked));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
|
||||
if (receiver.getVersion().getMinorVersion() < 8) return;
|
||||
if (!receiver.isLoaded() || isDisabledPlayer(receiver) || getDisabledUnlimitedPlayers().contains(receiver)) return;
|
||||
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
|
||||
packetListener.onEntityMove(receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
|
||||
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
|
||||
packetListener.onEntityMove(receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
|
||||
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
|
||||
packetListener.onEntitySpawn(receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
|
||||
} else if (PacketPlayOutEntityDestroyStorage.CLASS.isInstance(packet)) {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
Object entities = PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet);
|
||||
if (entities instanceof List) {
|
||||
packetListener.onEntityDestroy(receiver, (List<Integer>) entities);
|
||||
} else {
|
||||
//1.17.0
|
||||
packetListener.onEntityDestroy(receiver, (int) entities);
|
||||
}
|
||||
} else {
|
||||
packetListener.onEntityDestroy(receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areInSameWorld(TabPlayer player1, TabPlayer player2) {
|
||||
return ((Player) player1.getPlayer()).getWorld() == ((Player) player1.getPlayer()).getWorld();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSee(TabPlayer viewer, TabPlayer target) {
|
||||
return ((Player)viewer.getPlayer()).canSee((Player)target.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListener() {
|
||||
HandlerList.unregisterAll(eventListener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public List<Integer> getPassengers(Object entity) {
|
||||
Entity vehicle = (Entity) entity;
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 11) {
|
||||
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
|
||||
} else {
|
||||
if (vehicle.getPassenger() != null) {
|
||||
return Collections.singletonList(vehicle.getPassenger().getEntityId());
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerVehiclePlaceholder() {
|
||||
TabAPI.getInstance().getPlaceholderManager().registerPlayerPlaceholder(TabConstants.Placeholder.VEHICLE, 100, p -> {
|
||||
Entity v = ((Player)p.getPlayer()).getVehicle();
|
||||
//There's a bug in Bukkit 1.19.3 throwing NPE on .toString(), use default toString implementation
|
||||
return v == null ? "" : v.getClass().getName() + "@" + Integer.toHexString(v.hashCode());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getVehicle(TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getVehicle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEntityId(Object entity) {
|
||||
return ((Entity)entity).getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntityType(Object entity) {
|
||||
return ((Entity) entity).getType().toString().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking(TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).isSneaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSwimming(TabPlayer player) {
|
||||
Player p = (Player) player.getPlayer();
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
|
||||
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGliding(TabPlayer player) {
|
||||
return TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSleeping(TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).isSleeping();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getArmorStandType() {
|
||||
return EntityType.ARMOR_STAND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getX(TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getLocation().getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getY(Object entity) {
|
||||
return ((Entity)entity).getLocation().getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getZ(TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getLocation().getZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible, boolean markerFlag) {
|
||||
DataWatcher datawatcher = new DataWatcher();
|
||||
datawatcher.getHelper().setEntityFlags(flags);
|
||||
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
|
||||
datawatcher.getHelper().setCustomNameVisible(nameVisible);
|
||||
if (markerFlag) datawatcher.getHelper().setArmorStandFlags((byte)16);
|
||||
return datawatcher;
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Class listening to Bukkit events which the feature requires and are
|
||||
* sufficient, without packets being requires.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class EventListener implements Listener {
|
||||
|
||||
/** Reference to the main feature */
|
||||
private final BukkitNameTagX feature;
|
||||
|
||||
/**
|
||||
* Sneak event listener to de-spawn and spawn armor stands to skip animation
|
||||
*
|
||||
* @param e
|
||||
* sneak event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onSneak(PlayerToggleSneakEvent e) {
|
||||
TabPlayer p = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (p == null || feature.isPlayerDisabled(p)) return;
|
||||
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning armor stands as respawn screen destroys all entities in client
|
||||
*
|
||||
* @param e
|
||||
* respawn event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
|
||||
TabPlayer respawned = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (feature.isPlayerDisabled(respawned)) return;
|
||||
feature.getArmorStandManager(respawned).teleport();
|
||||
});
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package me.neznamy.tab.platforms.bukkit.hook;
|
||||
|
||||
import de.myzelyam.api.vanish.VanishAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* PremiumVanish hook for Bukkit.
|
||||
*/
|
||||
public class BukkitPremiumVanishHook extends PremiumVanishHook {
|
||||
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
return VanishAPI.canSee(((BukkitTabPlayer)viewer).getPlayer(), ((BukkitTabPlayer)target).getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished(@NotNull TabPlayer player) {
|
||||
return VanishAPI.isInvisible(((BukkitTabPlayer)player).getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package me.neznamy.tab.platforms.bukkit.hook;
|
||||
|
||||
import me.libraryaddict.disguise.DisguiseAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Class for hooking into LibsDisguises to get disguise status of players.
|
||||
*/
|
||||
public class LibsDisguisesHook {
|
||||
|
||||
/** Flag tracking if LibsDisguises is installed or not */
|
||||
private static boolean installed = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
|
||||
|
||||
/**
|
||||
* Returns {@code true} if LibsDisguises is installed and player is disguised,
|
||||
* {@code false} otherwise.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if disguised, {@code false} otherwise
|
||||
*/
|
||||
public static boolean isDisguised(TabPlayer player) {
|
||||
try {
|
||||
return installed && DisguiseAPI.isDisguised(((BukkitTabPlayer)player).getPlayer());
|
||||
} catch (LinkageError e) {
|
||||
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
|
||||
installed = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Class containing main NMS-related information and methods.
|
||||
*/
|
||||
public class BukkitReflection {
|
||||
|
||||
/** CraftBukkit package */
|
||||
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
|
||||
|
||||
/** Server version data */
|
||||
private static final ServerVersion serverVersion = detectServerVersion();
|
||||
|
||||
/** Flag determining whether the server version is at least 1.19.3 or not */
|
||||
@Getter
|
||||
private static final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
|
||||
/** Flag determining whether the server version is at least 1.19.4 or not */
|
||||
@Getter
|
||||
private static final boolean is1_19_4Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundBundlePacket");
|
||||
|
||||
/** Flag determining whether the server version is at least 1.20.2 or not */
|
||||
@Getter
|
||||
private static final boolean is1_20_2Plus = ReflectionUtils.classExists("net.minecraft.world.scores.DisplaySlot");
|
||||
|
||||
/** Flag determining whether the server version is at least 1.20.3 or not */
|
||||
@Getter
|
||||
private static final boolean is1_20_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundResetScorePacket");
|
||||
|
||||
private static ServerVersion detectServerVersion() {
|
||||
FunctionWithException<String, Class<?>> classFunction = name -> Class.forName("net.minecraft." + name);
|
||||
String[] array = Bukkit.getServer().getClass().getPackage().getName().split("\\.");
|
||||
int minorVersion;
|
||||
if (array.length > 3) {
|
||||
// Normal packaging
|
||||
String serverPackage = array[3];
|
||||
minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
if (minorVersion < 17) {
|
||||
ClassLoader loader = BukkitReflection.class.getClassLoader();
|
||||
classFunction = name -> loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
}
|
||||
} else {
|
||||
// Paper without CB relocation
|
||||
minorVersion = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]);
|
||||
}
|
||||
return new ServerVersion(classFunction, minorVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server's minor version, such as 20 for 1.20.4
|
||||
*
|
||||
* @return server's minor version
|
||||
*/
|
||||
public static int getMinorVersion() {
|
||||
return serverVersion.getMinorVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class with given potential names in same order. For 1.17+ it takes packaged class names
|
||||
* without "net.minecraft." prefix, for <1.17 it takes class name only.
|
||||
*
|
||||
* @param names
|
||||
* possible class names
|
||||
* @return class for specified names
|
||||
* @throws ClassNotFoundException
|
||||
* if class does not exist
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
|
||||
for (String name : names) {
|
||||
try {
|
||||
return serverVersion.getClass.apply(name);
|
||||
} catch (ClassNotFoundException | NullPointerException ignored) {
|
||||
// not the first class name in array
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns CraftBukkit class with given package and name.
|
||||
*
|
||||
* @param name
|
||||
* Package and name of the class
|
||||
* @return CraftBukkit class
|
||||
* @throws ClassNotFoundException
|
||||
* If class does not exist
|
||||
*/
|
||||
public static Class<?> getBukkitClass(@NotNull String name) throws ClassNotFoundException {
|
||||
return Class.forName(CRAFTBUKKIT_PACKAGE + "." + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class with server version information.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
private static class ServerVersion {
|
||||
|
||||
private final FunctionWithException<String, Class<?>> getClass;
|
||||
private final int minorVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Class for converting TAB component into NMS components (1.7+).
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class ComponentConverter {
|
||||
|
||||
/** Instance of the class */
|
||||
@Nullable
|
||||
public static ComponentConverter INSTANCE;
|
||||
|
||||
private final FunctionWithException<String, Object> newTextComponent;
|
||||
private final BiFunction<ChatModifier, Boolean, Object> convertModifier;
|
||||
|
||||
private final Class<?> ChatModifier = BukkitReflection.getClass("network.chat.ChatModifier", "ChatModifier");
|
||||
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("EnumChatFormat");
|
||||
private final Constructor<?> newChatModifier;
|
||||
private final Method ChatBaseComponent_addSibling;
|
||||
private final Field Component_modifier;
|
||||
private final List<Field> magicCodes = ReflectionUtils.getFields(ChatModifier, Boolean.class);
|
||||
|
||||
// 1.15-
|
||||
private Method ChatModifier_setColor;
|
||||
|
||||
// 1.16+
|
||||
private Method ChatHexColor_fromRGB;
|
||||
private Constructor<?> newMinecraftKey;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes, constructors and methods.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
private ComponentConverter() throws ReflectiveOperationException {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
Method IChatBaseComponent_b = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class);
|
||||
newTextComponent = text -> IChatBaseComponent_b.invoke(null, text);
|
||||
Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.IChatMutableComponent", "IChatMutableComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
|
||||
} else {
|
||||
Class<?> ChatComponentText = BukkitReflection.getClass("network.chat.ChatComponentText", "ChatComponentText");
|
||||
Constructor<?> newChatComponentText = ChatComponentText.getConstructor(String.class);
|
||||
newTextComponent = newChatComponentText::newInstance;
|
||||
Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.ChatBaseComponent", "ChatBaseComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatComponentText, IChatBaseComponent, IChatBaseComponent);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
Class<?> chatHexColor = BukkitReflection.getClass("network.chat.ChatHexColor", "ChatHexColor");
|
||||
Class<?> MinecraftKey = BukkitReflection.getClass("resources.MinecraftKey", "MinecraftKey");
|
||||
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ChatClickable", "ChatClickable");
|
||||
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.ChatHoverable", "ChatHoverable");
|
||||
newMinecraftKey = MinecraftKey.getConstructor(String.class);
|
||||
ChatHexColor_fromRGB = ReflectionUtils.getOnlyMethod(chatHexColor, chatHexColor, int.class);
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifier.getDeclaredConstructor(chatHexColor, Boolean.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, MinecraftKey));
|
||||
convertModifier = this::createModifierModern;
|
||||
} else {
|
||||
newChatModifier = ChatModifier.getConstructor();
|
||||
ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifier, ChatModifier, EnumChatFormat);
|
||||
convertModifier = (modifier, protocolVersion) -> createModifierLegacy(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TAB component to NMS component.
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param modern
|
||||
* Whether client supports RGB or not
|
||||
* @return Converted component
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object convert(@NotNull TabComponent component, boolean modern) {
|
||||
if (component instanceof SimpleComponent) return newTextComponent.apply(((SimpleComponent) component).getText());
|
||||
|
||||
StructuredComponent component1 = (StructuredComponent) component;
|
||||
Object nmsComponent = newTextComponent.apply(component1.getText());
|
||||
Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier(), modern));
|
||||
for (StructuredComponent extra : component1.getExtra()) {
|
||||
ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra, modern));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierModern(@NotNull ChatModifier modifier, boolean modern) {
|
||||
Object color = null;
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb());
|
||||
} else {
|
||||
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getLegacyColor().getRgb());
|
||||
}
|
||||
}
|
||||
return newChatModifier.newInstance(
|
||||
color,
|
||||
modifier.isBold(),
|
||||
modifier.isItalic(),
|
||||
modifier.isUnderlined(),
|
||||
modifier.isStrikethrough(),
|
||||
modifier.isObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
modifier.getFont() == null ? null : newMinecraftKey.newInstance(modifier.getFont())
|
||||
);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierLegacy(@NotNull ChatModifier modifier) {
|
||||
Object nmsModifier = newChatModifier.newInstance();
|
||||
if (modifier.getColor() != null) {
|
||||
ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, modifier.getColor().getLegacyColor().name()));
|
||||
}
|
||||
if (modifier.isBold()) magicCodes.get(0).set(nmsModifier, true);
|
||||
if (modifier.isItalic()) magicCodes.get(1).set(nmsModifier, true);
|
||||
if (modifier.isStrikethrough()) magicCodes.get(2).set(nmsModifier, true);
|
||||
if (modifier.isUnderlined()) magicCodes.get(3).set(nmsModifier, true);
|
||||
if (modifier.isObfuscated()) magicCodes.get(4).set(nmsModifier, true);
|
||||
return nmsModifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load component converter.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
INSTANCE = new ComponentConverter();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the component converter is available and throws an exception if not.
|
||||
*/
|
||||
public static void ensureAvailable() {
|
||||
if (INSTANCE == null) throw new IllegalStateException("Component converter is not available");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.util.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class for sending packets to players.
|
||||
*/
|
||||
public class PacketSender {
|
||||
|
||||
/** Function for sending packet */
|
||||
@NotNull
|
||||
private final BiConsumerWithException<Player, Object> send;
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load required classes, fields and methods.
|
||||
* If something fails, error is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something fails
|
||||
*/
|
||||
public PacketSender() throws ReflectiveOperationException {
|
||||
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet", "Packet");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl", "server.network.PlayerConnection", "PlayerConnection");
|
||||
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Method sendPacket;
|
||||
if (BukkitReflection.getMinorVersion() >= 7) {
|
||||
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
|
||||
} else {
|
||||
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
|
||||
}
|
||||
send = (player, packet) -> sendPacket.invoke(PLAYER_CONNECTION.get(getHandle.invoke(player)), packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends packet to specified player.
|
||||
* If something goes wrong, throws an exception.
|
||||
*
|
||||
* @param player
|
||||
* Player to send packet to
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void sendPacket(@NotNull Player player, @NotNull Object packet) {
|
||||
send.accept(player, packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class for retrieving ping of players.
|
||||
*/
|
||||
public class PingRetriever {
|
||||
|
||||
/** Ping getter function */
|
||||
private static FunctionWithException<Player, Integer> getPing;
|
||||
|
||||
/**
|
||||
* Attempts to load required classes, fields and methods and marks class as available.
|
||||
* If something fails, error message is printed and class is not marked as available.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
getPing = Player::getPing;
|
||||
} else {
|
||||
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Field PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
|
||||
getPing = player -> PING.getInt(getHandle.invoke(player));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getPing = p -> -1;
|
||||
BukkitUtils.compatibilityError(e, "getting player's ping", null, "%ping% returning -1");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player's ping. If ping getter is not available and fields
|
||||
* failed to load, returns {@code -1}. If an exception was throws by
|
||||
* reflective operation, it is re-thrown.
|
||||
*
|
||||
* @param player
|
||||
* Player to get ping of
|
||||
* @return Player's ping or {@code -1} if it failed
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static int getPing(@NotNull Player player) {
|
||||
return getPing.apply(player);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 512 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user