mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
2127
Commits
2.8.11-pre9
...
4.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af2700798e | ||
|
|
f9c280bbdc | ||
|
|
6ee31c2c3f | ||
|
|
cbd944f6f2 | ||
|
|
cc20748f31 | ||
|
|
c9545165aa | ||
|
|
05c10bf2e7 | ||
|
|
67aeae2094 | ||
|
|
1cc85a0647 | ||
|
|
988c0f8b4c | ||
|
|
62306fa5d6 | ||
|
|
641c5a49c3 | ||
|
|
5637402435 | ||
|
|
728d6c9a25 | ||
|
|
22d3ba2877 | ||
|
|
d1e032e010 | ||
|
|
80c4316826 | ||
|
|
3addeba256 | ||
|
|
c52db733b7 | ||
|
|
07a492e353 | ||
|
|
3c1ec478c2 | ||
|
|
f3f614c3de | ||
|
|
a6627ac6c6 | ||
|
|
8d1040518f | ||
|
|
ca5d17e6ec | ||
|
|
b9fab42510 | ||
|
|
22f1ef43c4 | ||
|
|
ffbd4bf26b | ||
|
|
4d6f93e975 | ||
|
|
cb45cff77f | ||
|
|
4718040345 | ||
|
|
afffefcce1 | ||
|
|
4580f47563 | ||
|
|
e51f4bb3a1 | ||
|
|
e8c8d1dee7 | ||
|
|
3559afaef7 | ||
|
|
fc29420eb3 | ||
|
|
023cd3fb75 | ||
|
|
2781d9f232 | ||
|
|
d31e5de449 | ||
|
|
245fcdf917 | ||
|
|
e3ef3ffa68 | ||
|
|
95c3608c3f | ||
|
|
7c2236dc96 | ||
|
|
b83ef0e352 | ||
|
|
2865871d04 | ||
|
|
7f7b76a11e | ||
|
|
52569e4c65 | ||
|
|
0a0ae6c83b | ||
|
|
6c8a41388e | ||
|
|
fe94e33d85 | ||
|
|
ca1be457cf | ||
|
|
550b6350ea | ||
|
|
76470c880d | ||
|
|
a315bd88b3 | ||
|
|
bb62930525 | ||
|
|
b7490f0002 | ||
|
|
42f3851339 | ||
|
|
34f39ac8d5 | ||
|
|
8c16330119 | ||
|
|
8323c21c3e | ||
|
|
2a3ca932a4 | ||
|
|
e339928caa | ||
|
|
3eeb025f23 | ||
|
|
f19c1f1baa | ||
|
|
003e6a6adb | ||
|
|
95d435d61c | ||
|
|
bbc4e1d767 | ||
|
|
ae46adf8b9 | ||
|
|
461ef988d3 | ||
|
|
307d21cc85 | ||
|
|
e2beac6570 | ||
|
|
75e455f078 | ||
|
|
19c832d370 | ||
|
|
9cd4afe1d4 | ||
|
|
3b00f07f78 | ||
|
|
0b5008c087 | ||
|
|
76f210db03 | ||
|
|
85c8d9f1b9 | ||
|
|
a795fc7f77 | ||
|
|
7742ab8220 | ||
|
|
3e235a8f18 | ||
|
|
30f9888f6a | ||
|
|
d1f8886e2b | ||
|
|
3c037f9aa6 | ||
|
|
c70346c57c | ||
|
|
bd40dd85af | ||
|
|
eacd929b0f | ||
|
|
0ff5f17b7d | ||
|
|
57fa01151e | ||
|
|
e7bc1fc74b | ||
|
|
897f35ab53 | ||
|
|
7f8b738a69 | ||
|
|
079302fb05 | ||
|
|
630fcc9e50 | ||
|
|
c1601ca487 | ||
|
|
d035b9cb80 | ||
|
|
8db5e4d8ba | ||
|
|
4d51bb39d7 | ||
|
|
7eb0231e08 | ||
|
|
6412461576 | ||
|
|
5732eb366b | ||
|
|
ed3df8bb68 | ||
|
|
787f531203 | ||
|
|
cf5d888901 | ||
|
|
1969e5f755 | ||
|
|
8d12ee0958 | ||
|
|
c3bbc89c5b | ||
|
|
625c7cf3c2 | ||
|
|
14f0321992 | ||
|
|
e5294c596a | ||
|
|
c83290a99a | ||
|
|
8c66310af0 | ||
|
|
5ae91bf89e | ||
|
|
d5dc1b7bdb | ||
|
|
864eee3349 | ||
|
|
88932e3a5e | ||
|
|
4ebc60ff00 | ||
|
|
b0cb63b05d | ||
|
|
1db40ee2d5 | ||
|
|
fd3f0ebd21 | ||
|
|
ce73069345 | ||
|
|
088f2748e3 | ||
|
|
d9a8c69f43 | ||
|
|
16ff503b6a | ||
|
|
68c78b9c73 | ||
|
|
0c9d78a644 | ||
|
|
99d22642e2 | ||
|
|
dc762eacf8 | ||
|
|
5a44f30140 | ||
|
|
82e86b0f8a | ||
|
|
9c4eb4359c | ||
|
|
827c380463 | ||
|
|
7c4fc324c8 | ||
|
|
46215f3304 | ||
|
|
1accc21365 | ||
|
|
b74c2f62f7 | ||
|
|
9d14c0c923 | ||
|
|
650fda5781 | ||
|
|
286ccf5836 | ||
|
|
dfa310666c | ||
|
|
06595241ab | ||
|
|
0f80dd1cfd | ||
|
|
7bc5be264c | ||
|
|
e31784fb1e | ||
|
|
ae156f424b | ||
|
|
cfe0b3883e | ||
|
|
fc6beaa0a5 | ||
|
|
5f2140b147 | ||
|
|
c497bdff39 | ||
|
|
7c84b6432e | ||
|
|
3f787c3b0b | ||
|
|
2046a39e99 | ||
|
|
2ccf89e396 | ||
|
|
efaebc3cb0 | ||
|
|
d7c952cc36 | ||
|
|
1d3ea37129 | ||
|
|
1e9c47398d | ||
|
|
5a1f48f1a5 | ||
|
|
ed65554d30 | ||
|
|
0f19970401 | ||
|
|
b419499710 | ||
|
|
14a5f30b74 | ||
|
|
302cdf200f | ||
|
|
bdde257cac | ||
|
|
73af013bca | ||
|
|
e421427007 | ||
|
|
169a180d68 | ||
|
|
16a19bf578 | ||
|
|
5b39204a8a | ||
|
|
b2bb125248 | ||
|
|
980354104b | ||
|
|
b27571dcaa | ||
|
|
b215bf9000 | ||
|
|
ab75b13b3f | ||
|
|
31cd2aeb08 | ||
|
|
8b6b33b314 | ||
|
|
1b66257ece | ||
|
|
bed9fcf212 | ||
|
|
692f309750 | ||
|
|
2c9c9b502a | ||
|
|
3da8fd2a77 | ||
|
|
ba6826f3d1 | ||
|
|
68b3629437 | ||
|
|
8bc5962d1c | ||
|
|
b8aa1982e4 | ||
|
|
e0a427732e | ||
|
|
8d1f60f5fa | ||
|
|
5cd7fdf385 | ||
|
|
e54d342ac5 | ||
|
|
9019261c2a | ||
|
|
b1a5092053 | ||
|
|
ef8bdbb40f | ||
|
|
0d67826b3d | ||
|
|
6c99e28d51 | ||
|
|
13931aafa5 | ||
|
|
089e7ce8b5 | ||
|
|
54287c8a29 | ||
|
|
5ebc5dee21 | ||
|
|
bedbdbcca9 | ||
|
|
a1c35adf4c | ||
|
|
81b6a9be8b | ||
|
|
88eee58329 | ||
|
|
2f02a5bda4 | ||
|
|
ba716df634 | ||
|
|
988292b82a | ||
|
|
d52cfdd6e6 | ||
|
|
b13a49539f | ||
|
|
32a39a361c | ||
|
|
0e2e9b9ae6 | ||
|
|
331faefde2 | ||
|
|
1603ebe9fc | ||
|
|
bad4359469 | ||
|
|
a860b463ed | ||
|
|
b3b0a96bb8 | ||
|
|
c211724d13 | ||
|
|
0fb9a832ca | ||
|
|
7ce1a97c93 | ||
|
|
1d461c1adf | ||
|
|
5ec09c37bc | ||
|
|
95a20766c3 | ||
|
|
87feb1c2bf | ||
|
|
8c732ac7e9 | ||
|
|
977c1f4d74 | ||
|
|
65ac22aadb | ||
|
|
8e7a14bdb8 | ||
|
|
4693a297b6 | ||
|
|
af69dcd9b5 | ||
|
|
71110fc722 | ||
|
|
c2af5632a2 | ||
|
|
399e813ca4 | ||
|
|
daac7c6171 | ||
|
|
940352ef2c | ||
|
|
c70b909c0f | ||
|
|
dcc2eb9217 | ||
|
|
5425b3b6ea | ||
|
|
a90ea3e3fa | ||
|
|
6d52b7b615 | ||
|
|
51635a7322 | ||
|
|
481b33ba55 | ||
|
|
22c785fc99 | ||
|
|
42d4159b69 | ||
|
|
6d95a2f06d | ||
|
|
7f0b1d8a2e | ||
|
|
a3fecd0fea | ||
|
|
f3d6a54a2a | ||
|
|
1d434f2557 | ||
|
|
b3500086e4 | ||
|
|
bf9202af38 | ||
|
|
bd2c683568 | ||
|
|
c03feaab23 | ||
|
|
9cb9ccbd2a | ||
|
|
4606ebd588 | ||
|
|
679be83c5a | ||
|
|
afe868bb95 | ||
|
|
c9ce3aa23e | ||
|
|
11a49c4874 | ||
|
|
65d2a0fba9 | ||
|
|
c1219195c7 | ||
|
|
fca0dfc91b | ||
|
|
7f1cc3063c | ||
|
|
e444c25287 | ||
|
|
476354e407 | ||
|
|
9f84d3f22f | ||
|
|
4608ff360c | ||
|
|
68b12e199d | ||
|
|
4b1151f769 | ||
|
|
6687214f2a | ||
|
|
a57757258e | ||
|
|
3a6060c45d | ||
|
|
ee375c1b60 | ||
|
|
c9d870cca0 | ||
|
|
3ad8015cc1 | ||
|
|
33609c322f | ||
|
|
431b89c487 | ||
|
|
35a146992c | ||
|
|
dedee114c4 | ||
|
|
9d42af0388 | ||
|
|
8f5d81bc33 | ||
|
|
b17f5563dd | ||
|
|
0ab8bb363a | ||
|
|
ac5fca86bd | ||
|
|
28c2c2e193 | ||
|
|
904c0b6a17 | ||
|
|
f3c31bc92c | ||
|
|
6016b84889 | ||
|
|
cfb5cb3a9f | ||
|
|
1746f6b1d0 | ||
|
|
9c3979a0d9 | ||
|
|
810676c69f | ||
|
|
d0892a245a | ||
|
|
f539b1b93b | ||
|
|
adfa64a29f | ||
|
|
d4f86ee79d | ||
|
|
6ebe31b737 | ||
|
|
dcbd5e62d4 | ||
|
|
a7e6e49363 | ||
|
|
d115257ff7 | ||
|
|
d578be276c | ||
|
|
e338f57441 | ||
|
|
c87321b5b0 | ||
|
|
84bb9b449b | ||
|
|
9d613fc24d | ||
|
|
3f2ae3131e | ||
|
|
0e9f53bd58 | ||
|
|
3981815b63 | ||
|
|
3ef99e4e7c | ||
|
|
76767a0958 | ||
|
|
1683943d58 | ||
|
|
cc0dbe8623 | ||
|
|
b056251e07 | ||
|
|
3b352c66e2 | ||
|
|
b4f0ea73c1 | ||
|
|
1c683788b5 | ||
|
|
250597df2d | ||
|
|
0d2d1d5aaa | ||
|
|
061375d1e7 | ||
|
|
5b05614305 | ||
|
|
69db2e0e53 | ||
|
|
00ef364324 | ||
|
|
de68312b6b | ||
|
|
ddac08bcbc | ||
|
|
517e285bb3 | ||
|
|
81a08a6b51 | ||
|
|
4dfef1d032 | ||
|
|
5d38e28cae | ||
|
|
66a12d10c4 | ||
|
|
1a4027c8d5 | ||
|
|
2a0243c51a | ||
|
|
4851d09e70 | ||
|
|
3cdf3accfd | ||
|
|
fd1f094216 | ||
|
|
25ece1ecb8 | ||
|
|
802e65c280 | ||
|
|
882688f056 | ||
|
|
97d60d0ab3 | ||
|
|
9ef99daed1 | ||
|
|
71c85d9d39 | ||
|
|
b981ab46be | ||
|
|
016633e494 | ||
|
|
1db862ab56 | ||
|
|
3563786310 | ||
|
|
ac8851dd6a | ||
|
|
53ab2d9793 | ||
|
|
2a58c86ffc | ||
|
|
fa46d527ae | ||
|
|
667bd38305 | ||
|
|
faba439466 | ||
|
|
c301157758 | ||
|
|
8d09d425a4 | ||
|
|
11f03d5a10 | ||
|
|
83625202a1 | ||
|
|
eb4b715a88 | ||
|
|
89dce941cf | ||
|
|
6371b67e9e | ||
|
|
966e27418a | ||
|
|
4adf7d1734 | ||
|
|
1a4c4a6fe8 | ||
|
|
7ff36592ae | ||
|
|
1b513412d2 | ||
|
|
f2bb43fbcd | ||
|
|
df5d07d885 | ||
|
|
0ad2f33c4c | ||
|
|
dee48fccb3 | ||
|
|
9ca372ab13 | ||
|
|
58a849a902 | ||
|
|
895072ec35 | ||
|
|
2b2c4d2c39 | ||
|
|
d534b6e2e8 | ||
|
|
783e9d9931 | ||
|
|
a109d9b55b | ||
|
|
5ce59ce44f | ||
|
|
1e9195a9bc | ||
|
|
3099d16468 | ||
|
|
c9c6af346c | ||
|
|
af80a43628 | ||
|
|
dc3c98ec91 | ||
|
|
dffa2c83f6 | ||
|
|
dc66f253a7 | ||
|
|
17498cbfc2 | ||
|
|
96e4d30168 | ||
|
|
c9d1934bb9 | ||
|
|
8bec083194 | ||
|
|
abf047be23 | ||
|
|
4a95399d56 | ||
|
|
885bf5154e | ||
|
|
3e146fc496 | ||
|
|
95710bcaf5 | ||
|
|
edd620f17d | ||
|
|
9c466d8b1e | ||
|
|
b385ab70fb | ||
|
|
140224840b | ||
|
|
2078cd5a5f | ||
|
|
27bae7fdbd | ||
|
|
174b4214de | ||
|
|
4ac524840c | ||
|
|
dd4ce4e2c3 | ||
|
|
2a03d69628 | ||
|
|
c28252e6ce | ||
|
|
da1768d833 | ||
|
|
b58c3e6b47 | ||
|
|
c7575bdc8f | ||
|
|
e275d22061 | ||
|
|
b5600ef22a | ||
|
|
b8e14c9256 | ||
|
|
1af5b9a308 | ||
|
|
2dd719c3a5 | ||
|
|
45d94694cd | ||
|
|
3ce8c6a192 | ||
|
|
7deb86f105 | ||
|
|
d9b4f6a43f | ||
|
|
54a7fb8721 | ||
|
|
d1dcb2f437 | ||
|
|
a92bab617a | ||
|
|
57e34bc595 | ||
|
|
953baa273c | ||
|
|
c403cb3187 | ||
|
|
ffd6dc5bb9 | ||
|
|
0f6504a3dc | ||
|
|
f376fea598 | ||
|
|
4896cdff91 | ||
|
|
b4c87431bc | ||
|
|
1cffbab97d | ||
|
|
9b0711075a | ||
|
|
17a8e902eb | ||
|
|
4bdb5ba245 | ||
|
|
fd7748d280 | ||
|
|
317b0292ff | ||
|
|
b87be649bc | ||
|
|
e2dda54011 | ||
|
|
d17518c39b | ||
|
|
48541dd0bd | ||
|
|
b83b023fee | ||
|
|
60e0c78033 | ||
|
|
a395f7f01f | ||
|
|
09c36ef69e | ||
|
|
9d5fa685a0 | ||
|
|
6794f593fa | ||
|
|
5e9965276f | ||
|
|
1473038955 | ||
|
|
2041ca57f3 | ||
|
|
098782d820 | ||
|
|
87e9a6defd | ||
|
|
3d896ff828 | ||
|
|
bf93240888 | ||
|
|
0b0e30c3c9 | ||
|
|
84d3816bd1 | ||
|
|
04ff606947 | ||
|
|
1cb8e6e238 | ||
|
|
4a2226170d | ||
|
|
1469124cfc | ||
|
|
0826095e73 | ||
|
|
1bd698adde | ||
|
|
1058d269ff | ||
|
|
bacd87b78e | ||
|
|
5befbe8780 | ||
|
|
e159b2adae | ||
|
|
52f7418853 | ||
|
|
76061f7ccd | ||
|
|
7d39e21eae | ||
|
|
fee3723474 | ||
|
|
2f0e4c6888 | ||
|
|
b04cb6d3c5 | ||
|
|
a9228a698a | ||
|
|
27102495d8 | ||
|
|
d1bf96ab33 | ||
|
|
3360dbb468 | ||
|
|
d224e7584e | ||
|
|
6373540076 | ||
|
|
5f78aca997 | ||
|
|
145d944d33 | ||
|
|
ac91940e3b | ||
|
|
ef8452be73 | ||
|
|
47e0810697 | ||
|
|
1f30f225c7 | ||
|
|
fcbfd60ddb | ||
|
|
46f9033713 | ||
|
|
7e65018a0e | ||
|
|
9ef3485968 | ||
|
|
257f8f1145 | ||
|
|
8d65cbb0e5 | ||
|
|
20d221e3ca | ||
|
|
94072acf10 | ||
|
|
0422cc2597 | ||
|
|
58f61e645b | ||
|
|
5f416bd9e5 | ||
|
|
64528c913c | ||
|
|
357e7c6a49 | ||
|
|
81dd202053 | ||
|
|
189797aa73 | ||
|
|
1ae2ef7cad | ||
|
|
de14e14d33 | ||
|
|
19892c14c1 | ||
|
|
2e00c4c002 | ||
|
|
97aa367629 | ||
|
|
87cc9a030b | ||
|
|
8ba83d1cb1 | ||
|
|
9f0e60bd47 | ||
|
|
93b2a25b2e | ||
|
|
4242d7a839 | ||
|
|
0ddb99bd46 | ||
|
|
969672e3ad | ||
|
|
2423fbb150 | ||
|
|
a99928dbf5 | ||
|
|
12f5dd52b4 | ||
|
|
2bd105446e | ||
|
|
6d85875fde | ||
|
|
42810aa490 | ||
|
|
bd31521bd3 | ||
|
|
ec0deb8378 | ||
|
|
ae66b6e9a1 | ||
|
|
3b86becebd | ||
|
|
4a8d86825b | ||
|
|
54a3bd81b4 | ||
|
|
b6768cca99 | ||
|
|
60c414deed | ||
|
|
009283a83f | ||
|
|
4a430b97df | ||
|
|
722333da2e | ||
|
|
1215af5262 | ||
|
|
254088c37b | ||
|
|
cb2ce4acca | ||
|
|
872adca8fd | ||
|
|
f37cc0bdd8 | ||
|
|
4063578ac3 | ||
|
|
2528de7eeb | ||
|
|
37caed3123 | ||
|
|
27fe9dfb12 | ||
|
|
0169cbd4d2 | ||
|
|
ed760aec08 | ||
|
|
858cbcefeb | ||
|
|
8a87d581a9 | ||
|
|
6d642f4e51 | ||
|
|
aca63640bc | ||
|
|
1845a35315 | ||
|
|
cab5f89a95 | ||
|
|
1777d8bf01 | ||
|
|
91b0bfaa9d | ||
|
|
f1cf994d10 | ||
|
|
69957be933 | ||
|
|
b78eb5efb6 | ||
|
|
3577ad1964 | ||
|
|
4510491177 | ||
|
|
01b3a12377 | ||
|
|
64ae9863fb | ||
|
|
ecd315a407 | ||
|
|
678eed288e | ||
|
|
9510455cb1 | ||
|
|
e925ce23d0 | ||
|
|
c68cad6a1e | ||
|
|
01d6eda4fb | ||
|
|
73e7119bbc | ||
|
|
cea781382a | ||
|
|
cf25b84e86 | ||
|
|
86e1d22390 | ||
|
|
1625be111f | ||
|
|
00cda3b2aa | ||
|
|
85a00a4436 | ||
|
|
37aec1a497 | ||
|
|
ef637616ee | ||
|
|
af3b5067fb | ||
|
|
53e5b15f96 | ||
|
|
6a3afae607 | ||
|
|
2e05c7d9d1 | ||
|
|
fb698a04b7 | ||
|
|
52bdf30ddb | ||
|
|
a92f07aa40 | ||
|
|
003ff7f99d | ||
|
|
47b9806ae7 | ||
|
|
d2a7c65e08 | ||
|
|
e232336a5f | ||
|
|
8816c75212 | ||
|
|
f9d54751ba | ||
|
|
43d41ee761 | ||
|
|
4512ba8d5e | ||
|
|
8b55bca564 | ||
|
|
d8d54970d3 | ||
|
|
15ea4482f9 | ||
|
|
87f9f38eac | ||
|
|
dba559f041 | ||
|
|
c610b6f323 | ||
|
|
a5fb900e19 | ||
|
|
09d6e455f5 | ||
|
|
e68097b0f4 | ||
|
|
0744fed953 | ||
|
|
de0adf4067 | ||
|
|
76bf2b3efc | ||
|
|
c089badeb4 | ||
|
|
b3b99d366a | ||
|
|
4a36486f65 | ||
|
|
75126d00d9 | ||
|
|
17ebf95d5b | ||
|
|
c764420cfe | ||
|
|
28f05fc2af | ||
|
|
a420892887 | ||
|
|
d910dc5913 | ||
|
|
c9c1cbc349 | ||
|
|
fd4704f75e | ||
|
|
5400ca637f | ||
|
|
79d2610117 | ||
|
|
6a51f4cb9d | ||
|
|
c190776665 | ||
|
|
3765a1232e | ||
|
|
28866eba21 | ||
|
|
08adee3dc3 | ||
|
|
8a1adaf2cd | ||
|
|
fdb9accaa6 | ||
|
|
4f7656748b | ||
|
|
03e4495ac7 | ||
|
|
01af5cb9e1 | ||
|
|
8d49f32df2 | ||
|
|
8a955b3bc8 | ||
|
|
ead40cb413 | ||
|
|
5e07f78d4f | ||
|
|
c7d98795bc | ||
|
|
9f637e3d6a | ||
|
|
2d5f074c78 | ||
|
|
3be6ba484d | ||
|
|
d9d74e7a32 | ||
|
|
887306a5ed | ||
|
|
4b2542eb4f | ||
|
|
66bd3baa3e | ||
|
|
bf32004f8f | ||
|
|
bfb2df9c95 | ||
|
|
ddeb05e408 | ||
|
|
9312d61258 | ||
|
|
d44909bc42 | ||
|
|
1d77df335c | ||
|
|
a268dc25f4 | ||
|
|
8c5696faa0 | ||
|
|
94f3ccfb7d | ||
|
|
576329214d | ||
|
|
5c13c2e225 | ||
|
|
1070f85617 | ||
|
|
7c9dc87fcb | ||
|
|
363e58bf91 | ||
|
|
daca3f5b71 | ||
|
|
a7d2ec084b | ||
|
|
167f8cd0ff | ||
|
|
170307f45f | ||
|
|
2f36377780 | ||
|
|
48c565d85d | ||
|
|
86b3cc4044 | ||
|
|
6a2d5091b8 | ||
|
|
dc09e77190 | ||
|
|
4fc139c0c6 | ||
|
|
e5d800f469 | ||
|
|
4879ac28b5 | ||
|
|
bd0badab48 | ||
|
|
5575a26d3b | ||
|
|
d8cbe96949 | ||
|
|
cfb506a7ff | ||
|
|
d5b0ff7f40 | ||
|
|
31d259210c | ||
|
|
39112b8d41 | ||
|
|
4c92286ebb | ||
|
|
a317aeba85 | ||
|
|
dcaf17294e | ||
|
|
0bc734689a | ||
|
|
d0c2c00cc9 | ||
|
|
fd02328af9 | ||
|
|
3b7de31324 | ||
|
|
ebd67dd025 | ||
|
|
4557a39ae2 | ||
|
|
ce1eb299d3 | ||
|
|
20ab3f0a72 | ||
|
|
d61f90c25c | ||
|
|
90474e7592 | ||
|
|
a2e28003bc | ||
|
|
6e409c3dcd | ||
|
|
85a699c8de | ||
|
|
a788d1fb23 | ||
|
|
ab5b72375c | ||
|
|
3a023c51b5 | ||
|
|
0ad91bcafe | ||
|
|
f41ea3bcab | ||
|
|
327dc4b416 | ||
|
|
27e7779456 | ||
|
|
a7b2f5bb22 | ||
|
|
19fcb7c946 | ||
|
|
2706cd5881 | ||
|
|
4e2abd9c5b | ||
|
|
51ba2fa80a | ||
|
|
fd60cd0a21 | ||
|
|
5025372c05 | ||
|
|
4806bd075f | ||
|
|
370d17757a | ||
|
|
a648d7e75f | ||
|
|
518e1c5310 | ||
|
|
11539d8290 | ||
|
|
e4f33b58f7 | ||
|
|
073caa9515 | ||
|
|
2b06b11916 | ||
|
|
032b0f8a41 | ||
|
|
9391363f9b | ||
|
|
467fb2e9c7 | ||
|
|
257893b8de | ||
|
|
e919839de5 | ||
|
|
77d372cbee | ||
|
|
03e9facaf7 | ||
|
|
f3e459d911 | ||
|
|
36feff0e04 | ||
|
|
0320140a1b | ||
|
|
4fb1b1c9f9 | ||
|
|
37f3896a41 | ||
|
|
e1c152d95d | ||
|
|
dffac5deb8 | ||
|
|
8aa3bbfad3 | ||
|
|
5bb4006e55 | ||
|
|
e06a406682 | ||
|
|
a19719be4b | ||
|
|
35856a9f3d | ||
|
|
cf1d5d5ee2 | ||
|
|
f69a845e51 | ||
|
|
db67e93bb8 | ||
|
|
313f089222 | ||
|
|
458c08ded7 | ||
|
|
fe6cc7417f | ||
|
|
181c94a808 | ||
|
|
9ff6f02d8d | ||
|
|
3cf74516d9 | ||
|
|
31f5502c16 | ||
|
|
671d60bd72 | ||
|
|
294e866501 | ||
|
|
96789100e3 | ||
|
|
ea934fc7fb | ||
|
|
d529b7b340 | ||
|
|
092ca7f9f8 | ||
|
|
79f4bdb869 | ||
|
|
b0d2d0f7dc | ||
|
|
cdf58c1dfe | ||
|
|
4680de6022 | ||
|
|
e8f61181b6 | ||
|
|
75e9e007d0 | ||
|
|
3574ef7c0b | ||
|
|
86158036d2 | ||
|
|
e73beb47a3 | ||
|
|
0db5f1dd73 | ||
|
|
6dcdc2aa58 | ||
|
|
64601137e8 | ||
|
|
af7697b670 | ||
|
|
9e4f46088b | ||
|
|
91e71a9b9c | ||
|
|
1198ffb847 | ||
|
|
72116ecd0c | ||
|
|
04ebb7123d | ||
|
|
43096beafe | ||
|
|
2a08c62456 | ||
|
|
b62d74c3fa | ||
|
|
652fb5a6ad | ||
|
|
d1f25cc19f | ||
|
|
dc6a9d9761 | ||
|
|
838d725715 | ||
|
|
c45acb39ca | ||
|
|
957edf2a52 | ||
|
|
d969c6ed0e | ||
|
|
dd6fca961c | ||
|
|
e88a6df456 | ||
|
|
3a4530c999 | ||
|
|
b623dba40a | ||
|
|
e2fd945109 | ||
|
|
e6dc3cda31 | ||
|
|
18d8ff127a | ||
|
|
4a9526451f | ||
|
|
669d89c870 | ||
|
|
39161f2af5 | ||
|
|
be8c33e3fc | ||
|
|
7c1a27f9df | ||
|
|
342cc93dbf | ||
|
|
6f07cf439b | ||
|
|
1d4d00b69b | ||
|
|
54cb3e4d54 | ||
|
|
b0fc2caef9 | ||
|
|
1221509d24 | ||
|
|
17f5f60504 | ||
|
|
a8b0b488c6 | ||
|
|
6d922d5b70 | ||
|
|
1ab62a33e3 | ||
|
|
f9540a1e78 | ||
|
|
a720bf2c8b | ||
|
|
c143eab6fb | ||
|
|
1afb20af8b | ||
|
|
c6f27c839d | ||
|
|
742f1708c5 | ||
|
|
ac9601a48b | ||
|
|
4cd109a5fc | ||
|
|
078554a8b0 | ||
|
|
464e2f4f64 | ||
|
|
b84f55aa18 | ||
|
|
be2f780bfa | ||
|
|
262d8493a6 | ||
|
|
24e12e871b | ||
|
|
a2ee75339c | ||
|
|
9c28c4d63c | ||
|
|
7d57da1424 | ||
|
|
b77b20ba4d | ||
|
|
57ae58f093 | ||
|
|
b29c878688 | ||
|
|
5a5a6c76c1 | ||
|
|
f6a53f537c | ||
|
|
d86ed37208 | ||
|
|
92b945f28d | ||
|
|
cb164f0055 | ||
|
|
acfe37c1b5 | ||
|
|
44ded26df4 | ||
|
|
6dbd92a010 | ||
|
|
f729ca0ad2 | ||
|
|
065490c077 | ||
|
|
da6425dd93 | ||
|
|
250dcbc676 | ||
|
|
de4ca4a88f | ||
|
|
723a0c890e | ||
|
|
7085e7df20 | ||
|
|
09496d60f3 | ||
|
|
bf6bc26a5c | ||
|
|
a7217eae15 | ||
|
|
2a532ef9b7 | ||
|
|
c304ded924 | ||
|
|
4d8c47a505 | ||
|
|
d3e0ccbf34 | ||
|
|
3c82b46148 | ||
|
|
07d7f2cc17 | ||
|
|
54d4f09ab1 | ||
|
|
e8c698b5e8 | ||
|
|
b096906ffd | ||
|
|
5a9cec12fa | ||
|
|
a2ed71751f | ||
|
|
5a3a2df920 | ||
|
|
9d07e669ea | ||
|
|
aa18f1cc35 | ||
|
|
668d78ea64 | ||
|
|
6f62581d6e | ||
|
|
06686ada5c | ||
|
|
4380482606 | ||
|
|
d5112b3d32 | ||
|
|
ef67506640 | ||
|
|
c2ea4d2793 | ||
|
|
f0d8d479bd | ||
|
|
2d46d70c92 | ||
|
|
4320bb7cb4 | ||
|
|
398e8ed5ff | ||
|
|
4e8b804337 | ||
|
|
de7c664e0d | ||
|
|
c7600a2e3d | ||
|
|
8b34e82aab | ||
|
|
c96efe8802 | ||
|
|
0a1bfd3a2e | ||
|
|
755cf9ce43 | ||
|
|
c6aca74396 | ||
|
|
b5b7f3c16c | ||
|
|
293162b484 | ||
|
|
bd543b7b9d | ||
|
|
994fe9f482 | ||
|
|
f657bdb14c | ||
|
|
a4238e1191 | ||
|
|
daf08423b4 | ||
|
|
91367f3d3a | ||
|
|
9dbd52b638 | ||
|
|
22c6be2770 | ||
|
|
fb261c9f90 | ||
|
|
638e35bf90 | ||
|
|
25d13862ae | ||
|
|
1c8f483138 | ||
|
|
92ad215873 | ||
|
|
7ed3cfbb7c | ||
|
|
53e75a27be | ||
|
|
1b67819c3c | ||
|
|
07f8020f79 | ||
|
|
b12d0680d8 | ||
|
|
64247d913e | ||
|
|
3575cdae85 | ||
|
|
f71a542247 | ||
|
|
46cc59138b | ||
|
|
46bfa5112e | ||
|
|
fcdff51783 | ||
|
|
a4be4ae419 | ||
|
|
7083352b9a | ||
|
|
956a0f46c0 | ||
|
|
aea1864627 | ||
|
|
803b87111a | ||
|
|
f64a641b63 | ||
|
|
7f9bb354d7 | ||
|
|
4b7b6284f8 | ||
|
|
bbccd26415 | ||
|
|
bab7c737b3 | ||
|
|
3169787b41 | ||
|
|
f29bbc6657 | ||
|
|
f53cd5ae11 | ||
|
|
38d36b5ab5 | ||
|
|
8f8f596abc | ||
|
|
2afc8abdb2 | ||
|
|
38dd948306 | ||
|
|
e393cf0a10 | ||
|
|
4f451017e3 | ||
|
|
9cb31af48a | ||
|
|
2c17041b27 | ||
|
|
72c75a443e | ||
|
|
1ba6384647 | ||
|
|
e2edf9a37c | ||
|
|
cb0bfd095d | ||
|
|
ec03dd4755 | ||
|
|
de44f5ae42 | ||
|
|
5ee6796172 | ||
|
|
d034fca513 | ||
|
|
f65c3fb29a | ||
|
|
7192bc9d69 | ||
|
|
d7636a05f4 | ||
|
|
bd93301634 | ||
|
|
b0558ba0a2 | ||
|
|
251934bb4b | ||
|
|
f2c9ef5d30 | ||
|
|
15a072bbaa | ||
|
|
8f1c303862 | ||
|
|
c79d2e3e6e | ||
|
|
f45d8d860b | ||
|
|
88f31184a0 | ||
|
|
d5368baab4 | ||
|
|
10f645226c | ||
|
|
322c572cd6 | ||
|
|
fdfeb178b5 | ||
|
|
1c63cdba91 | ||
|
|
289d0db8e3 | ||
|
|
9adacca77f | ||
|
|
7dc6813274 | ||
|
|
77bf1cfbb9 | ||
|
|
8f28350579 | ||
|
|
2d06d0c1d3 | ||
|
|
7e8301afcd | ||
|
|
2600e8f856 | ||
|
|
914748e14e | ||
|
|
21a7b633e7 | ||
|
|
a77d4352e2 | ||
|
|
a7a21e0e51 | ||
|
|
a0a14cd285 | ||
|
|
44b7bedd53 | ||
|
|
409a0808c1 | ||
|
|
4214d02fad | ||
|
|
bc1abb1b58 | ||
|
|
2f2f53ba4d | ||
|
|
cbeb4c151d | ||
|
|
1d5ca70790 | ||
|
|
1f619ff4d3 | ||
|
|
f5acdebe60 | ||
|
|
bd59922204 | ||
|
|
c4d9482c79 | ||
|
|
8faef8f017 | ||
|
|
284161db77 | ||
|
|
78db2b66ef | ||
|
|
0d71643ecb | ||
|
|
9a7a105e7b | ||
|
|
f54d024d97 | ||
|
|
94338bd53f | ||
|
|
1a9ecc0313 | ||
|
|
4baeb7b945 | ||
|
|
baee479fb5 | ||
|
|
5ad4b2e9c4 | ||
|
|
26657cddfc | ||
|
|
947a922f8c | ||
|
|
a2be76184e | ||
|
|
8ddb733dc3 | ||
|
|
94e016129b | ||
|
|
5e653ded5d | ||
|
|
1bf8097c3f | ||
|
|
3d2f11ffd2 | ||
|
|
5e77a22d7a | ||
|
|
1dffcb1dc3 | ||
|
|
edc18cbafb | ||
|
|
2f23194432 | ||
|
|
20709472be | ||
|
|
7a8c6a0cec | ||
|
|
88558e40a7 | ||
|
|
79386b2b6b | ||
|
|
3aad8d3cb5 | ||
|
|
272a6f37ad | ||
|
|
e10081025d | ||
|
|
e03a6d8f11 | ||
|
|
98e7dc0321 | ||
|
|
74438c5d74 | ||
|
|
0c4e548565 | ||
|
|
cc70e01e96 | ||
|
|
7c635748af | ||
|
|
0c874ad56d | ||
|
|
2fb7f80b71 | ||
|
|
3f7c3414ce | ||
|
|
97165db44d | ||
|
|
82cdf04cf0 | ||
|
|
171d899609 | ||
|
|
6432278d5d | ||
|
|
ccaff12451 | ||
|
|
11347c080b | ||
|
|
553676c3f9 | ||
|
|
40b70e7418 | ||
|
|
0ea75019ae | ||
|
|
dbc092df77 | ||
|
|
68049f0dca | ||
|
|
9db5dfc175 | ||
|
|
039a316b3b | ||
|
|
f4b529f1ce | ||
|
|
9c6d606103 | ||
|
|
6cdc06aae7 | ||
|
|
2684654e7b | ||
|
|
25bcc33204 | ||
|
|
e5b3f6215b | ||
|
|
9306eadd90 | ||
|
|
a27fbb5b8b | ||
|
|
cd4c82b0b8 | ||
|
|
aa6179ee9e | ||
|
|
6594b8c2dc | ||
|
|
d4c2cdf003 | ||
|
|
2b529c37f5 | ||
|
|
8599cd925b | ||
|
|
89f37b4b9e | ||
|
|
f3136e85ee | ||
|
|
845743073c | ||
|
|
b2c7bacaf9 | ||
|
|
ed1673f032 | ||
|
|
bac9d443ea | ||
|
|
90e3868c08 | ||
|
|
c428572e01 | ||
|
|
884b5755ec | ||
|
|
32d7366795 | ||
|
|
21bf4a5450 | ||
|
|
8042b02cf5 | ||
|
|
ae7cdd3982 | ||
|
|
9ba945ad76 | ||
|
|
9cd04c3660 | ||
|
|
444bc19781 | ||
|
|
eb08c4ba04 | ||
|
|
5846e24a29 | ||
|
|
9c2c09e433 | ||
|
|
3c4329697b | ||
|
|
af9951d993 | ||
|
|
630f6df2f5 | ||
|
|
fcf07b8e7b | ||
|
|
4824c2f626 | ||
|
|
2b6b2e1b63 | ||
|
|
6926c1e57f | ||
|
|
6a00a7f1ca | ||
|
|
288c3d4c87 | ||
|
|
ac497bcc41 | ||
|
|
7b8d0d6845 | ||
|
|
a9a4589fae | ||
|
|
b8f972a774 | ||
|
|
305e388478 | ||
|
|
97e7848a78 | ||
|
|
d33058a924 | ||
|
|
eda3cc1f17 | ||
|
|
47043ad4ad | ||
|
|
acf20088cc | ||
|
|
374cb2766c | ||
|
|
31685b9ba5 | ||
|
|
722ff4ce52 | ||
|
|
161b375043 | ||
|
|
2c14191cde | ||
|
|
486546918f | ||
|
|
23d08e58a4 | ||
|
|
4c4c34ecaa | ||
|
|
bd3f2fd70a | ||
|
|
b80e42a635 | ||
|
|
9446a034d0 | ||
|
|
da0c6ab3fd | ||
|
|
c1adb27ee5 | ||
|
|
980d3027e3 | ||
|
|
53ba533f9b | ||
|
|
e40827da65 | ||
|
|
b9848b6a96 | ||
|
|
147593b11a | ||
|
|
21652f928b | ||
|
|
5b0352aff0 | ||
|
|
b2da2fa096 | ||
|
|
0cbb01a2a8 | ||
|
|
179840968d | ||
|
|
3b9c1649a1 | ||
|
|
5750c7dbe1 | ||
|
|
7ceca359be | ||
|
|
0f8152a6ed | ||
|
|
b3258bb020 | ||
|
|
4b2e705cbc | ||
|
|
31e41382f8 | ||
|
|
ecda7192dc | ||
|
|
9a48562b9f | ||
|
|
c181b58bc1 | ||
|
|
2624afa2ed | ||
|
|
3690af1e35 | ||
|
|
98966d3b38 | ||
|
|
41fdd16ce1 | ||
|
|
1bc94859c0 | ||
|
|
60f6f36437 | ||
|
|
82267ba16a | ||
|
|
14260deef8 | ||
|
|
8ba7342ccf | ||
|
|
f6b935d78e | ||
|
|
8cf3ae2091 | ||
|
|
e02b997521 | ||
|
|
229cef13c9 | ||
|
|
f503d6f896 | ||
|
|
d851166bb7 | ||
|
|
638a861350 | ||
|
|
20d7e4cbb0 | ||
|
|
36ccedd7c6 | ||
|
|
31854ba7a6 | ||
|
|
2639f07f5c | ||
|
|
b6e13f557f | ||
|
|
6333d90059 | ||
|
|
fea5d0ec56 | ||
|
|
c77f02d1fd | ||
|
|
c498965cae | ||
|
|
cdaa77071d | ||
|
|
6a72678c8d | ||
|
|
bb67fc622f | ||
|
|
5b50c02d93 | ||
|
|
ffec7e3266 | ||
|
|
039c1e7027 | ||
|
|
b27a21b3b8 | ||
|
|
2bcb5f617e | ||
|
|
7bf5294f9f | ||
|
|
c2fd1940f0 | ||
|
|
2d095ce6db | ||
|
|
e88bc36fca | ||
|
|
5613ea4f77 | ||
|
|
9c41599d60 | ||
|
|
5f8dd869e0 | ||
|
|
fb17df0b7e | ||
|
|
900ae83028 | ||
|
|
2c09db25b7 | ||
|
|
004e53fa29 | ||
|
|
e285b143d5 | ||
|
|
f78eb0ffed | ||
|
|
4d761e1d6e | ||
|
|
4a8c975319 | ||
|
|
fe56b7ed2d | ||
|
|
5a74654807 | ||
|
|
533ce4a529 | ||
|
|
4ea3780114 | ||
|
|
0d229ee26c | ||
|
|
29079ecad5 | ||
|
|
ea88401a58 | ||
|
|
298cef4d1f | ||
|
|
a51e9495de | ||
|
|
eead8ca1e2 | ||
|
|
a30712bc38 | ||
|
|
07b443fb21 | ||
|
|
61d927305c | ||
|
|
8f21c2f27d | ||
|
|
9fcf76e0c4 | ||
|
|
773afe2fb5 | ||
|
|
83ce5bd476 | ||
|
|
75a341c240 | ||
|
|
921a793b5c | ||
|
|
c1b92868cd | ||
|
|
94d19d728d | ||
|
|
8ef518f7e2 | ||
|
|
79fd1148a9 | ||
|
|
313ba8d765 | ||
|
|
c12c422357 | ||
|
|
b14c97d18a | ||
|
|
2fe345cb65 | ||
|
|
96ff81370c | ||
|
|
c4a1f41673 | ||
|
|
ca4c8ccb20 | ||
|
|
7471006f8a | ||
|
|
896c709824 | ||
|
|
473a37cb6a | ||
|
|
9e84d96051 | ||
|
|
019466d84d | ||
|
|
c222061529 | ||
|
|
acdd99404c | ||
|
|
799381bff3 | ||
|
|
b3654977ca | ||
|
|
b67d1928b5 | ||
|
|
052748d2b9 | ||
|
|
e265187ff8 | ||
|
|
c622a95550 | ||
|
|
94d71a245b | ||
|
|
a9403269be | ||
|
|
dfe6768eb5 | ||
|
|
d88bf65806 | ||
|
|
30929062d6 | ||
|
|
747e3d692d | ||
|
|
1d3ecbb7ed | ||
|
|
39b88b0dd5 | ||
|
|
d13d3eb055 | ||
|
|
f8f72b9074 | ||
|
|
a9d0b0a0ae | ||
|
|
37bd624537 | ||
|
|
e19f4ed771 | ||
|
|
c828fa8d52 | ||
|
|
9e080a5f5c | ||
|
|
7d7d4c05ab | ||
|
|
d4c7044708 | ||
|
|
52dd8c7c8b | ||
|
|
398ac8b6cf | ||
|
|
911792b214 | ||
|
|
8e54989502 | ||
|
|
4f16579063 | ||
|
|
e1f1698413 | ||
|
|
5eb999b98a | ||
|
|
5e7af835d8 | ||
|
|
f19f2c5a20 | ||
|
|
ff2aceb781 | ||
|
|
dc1410adbb | ||
|
|
49af38f3c5 | ||
|
|
09cf4dcd07 | ||
|
|
95c92285b9 | ||
|
|
81b7be9205 | ||
|
|
c308cde139 | ||
|
|
49889cd2cc | ||
|
|
d0ea71f0c9 | ||
|
|
a6f93dae1e | ||
|
|
f968999393 | ||
|
|
37348ed1f4 | ||
|
|
8602d39d7c | ||
|
|
fd41390e20 | ||
|
|
712064bced | ||
|
|
771a696de0 | ||
|
|
a56cbcc2f4 | ||
|
|
8e659a2c97 | ||
|
|
423bd0adb8 | ||
|
|
1987848f8b | ||
|
|
13ac8541f2 | ||
|
|
0e42d61ffa | ||
|
|
64b4cb1bb9 | ||
|
|
efd723deec | ||
|
|
4c2d2068a4 | ||
|
|
32529dd7e9 | ||
|
|
3ad54d5f21 | ||
|
|
6d81c37645 | ||
|
|
da1c51eb5b | ||
|
|
70c37fb9f2 | ||
|
|
5e565be35b | ||
|
|
eeb39ca228 | ||
|
|
2d5bc2bc52 | ||
|
|
4fb517c8cb | ||
|
|
de376cd9f4 | ||
|
|
d939984f08 | ||
|
|
89edcb7a38 | ||
|
|
ac16a0fb26 | ||
|
|
9189fb2082 | ||
|
|
a2e7670560 | ||
|
|
7c9f14b4d9 | ||
|
|
f05849aada | ||
|
|
66e91ee4e8 | ||
|
|
015d8de101 | ||
|
|
69e575e0ef | ||
|
|
6c84e49a7f | ||
|
|
f4e5e8fd72 | ||
|
|
fe8c738196 | ||
|
|
665dfac5ea | ||
|
|
57f9806819 | ||
|
|
7874135014 | ||
|
|
f86e7b17ef | ||
|
|
3803173745 | ||
|
|
6af30bf6df | ||
|
|
7bea6a214a | ||
|
|
9becc35fa1 | ||
|
|
7212a4cd7f | ||
|
|
c604640ab9 | ||
|
|
23fee2b7b7 | ||
|
|
c3d521960e | ||
|
|
484396cce1 | ||
|
|
ce4dec6136 | ||
|
|
67f42f518e | ||
|
|
0a0e947282 | ||
|
|
8a4127574a | ||
|
|
b29d3e5d6a | ||
|
|
daaece8d36 | ||
|
|
0e5b1aaf0e | ||
|
|
e672e49435 | ||
|
|
b9a38a486d | ||
|
|
d34c46658d | ||
|
|
e120b647ea | ||
|
|
936b8ae903 | ||
|
|
147fe2c277 | ||
|
|
84413aa490 | ||
|
|
3fea95f8c5 | ||
|
|
b839bce010 | ||
|
|
fc70c098a9 | ||
|
|
7b74d73a9a | ||
|
|
44ee3bbdfe | ||
|
|
673285baf8 | ||
|
|
2b22e327e3 | ||
|
|
95d65236f6 | ||
|
|
8971172444 | ||
|
|
6ff5836ca9 | ||
|
|
8e9a4e9601 | ||
|
|
1ce5c05622 | ||
|
|
7f83e83aed | ||
|
|
e677e7345b | ||
|
|
ae52aa2c55 | ||
|
|
b83f6e93bf | ||
|
|
3606ee6275 | ||
|
|
70571cccfe | ||
|
|
87fe02b0b4 | ||
|
|
8e48087f65 | ||
|
|
4e7c3ee2f1 | ||
|
|
60a7549662 | ||
|
|
fe7c9488d8 | ||
|
|
b7d39e55b6 | ||
|
|
805d8030b4 | ||
|
|
4c6fc5d2d2 | ||
|
|
913b8ee238 | ||
|
|
3688e4d938 | ||
|
|
03842db57f | ||
|
|
6f34157a84 | ||
|
|
86a82faa15 | ||
|
|
2e506b72a6 | ||
|
|
d2ba0a22d5 | ||
|
|
cfc3488a8c | ||
|
|
0463e82470 | ||
|
|
93b151b377 | ||
|
|
1c6fa28d87 | ||
|
|
71d01263b5 | ||
|
|
23268907bd | ||
|
|
076e3061b3 | ||
|
|
d9f8fa2ed0 | ||
|
|
c33f9a6d61 | ||
|
|
07f71f3d4d | ||
|
|
48f8a4c718 | ||
|
|
ff29b1badf | ||
|
|
05d3233c03 | ||
|
|
203b5d925b | ||
|
|
b2f6338b0b | ||
|
|
26e526d5fe | ||
|
|
054d6c58cb | ||
|
|
cc6b5ea0f5 | ||
|
|
495985ace8 | ||
|
|
a304853a7b | ||
|
|
eb2c58f0e9 | ||
|
|
125429bccd | ||
|
|
c5bf1b65ba | ||
|
|
5286a018df | ||
|
|
c408744385 | ||
|
|
5efd5f310f | ||
|
|
e35c14d8e3 | ||
|
|
d49fcc2307 | ||
|
|
afc1dfcf10 | ||
|
|
2dd06ed0c3 | ||
|
|
2ca223cd2c | ||
|
|
f6cf018625 | ||
|
|
1d7806c96e | ||
|
|
650fd892f8 | ||
|
|
83710b14b1 | ||
|
|
1eeb22f579 | ||
|
|
1d70e73106 | ||
|
|
9d93b52be1 | ||
|
|
4431772007 | ||
|
|
f17fafbb48 | ||
|
|
8d30399f33 | ||
|
|
5feb934a4a | ||
|
|
9466b96f69 | ||
|
|
ad00d1b145 | ||
|
|
89ea9ba8a0 | ||
|
|
e08fb3eeb6 | ||
|
|
5800c8910e | ||
|
|
1ec77cb0b5 | ||
|
|
3329514066 | ||
|
|
addc3f322f | ||
|
|
79dcd16d37 | ||
|
|
802c72a9aa | ||
|
|
bf96676df5 | ||
|
|
d245b93196 | ||
|
|
6f96b555e9 | ||
|
|
872700d1be | ||
|
|
1ac08ee1f2 | ||
|
|
ada433150c | ||
|
|
ee40a69e57 | ||
|
|
d6fcaffa99 | ||
|
|
fe4bdda549 | ||
|
|
6a1016a383 | ||
|
|
c53f08d0a7 | ||
|
|
2807ff30e6 | ||
|
|
cf3b9c21dd | ||
|
|
ee616412d6 | ||
|
|
341c6f8722 | ||
|
|
978df99e5c | ||
|
|
a91e2a90b4 | ||
|
|
49c54c6a58 | ||
|
|
6b72163dbe | ||
|
|
93a04c64d2 | ||
|
|
d01d814778 | ||
|
|
af15c256b9 | ||
|
|
dde0056ba2 | ||
|
|
dede5b548c | ||
|
|
7be0831e7c | ||
|
|
660275dc74 | ||
|
|
957e01fbf2 | ||
|
|
b01ffaf763 | ||
|
|
8d6d53a448 | ||
|
|
8f13bfcb4a | ||
|
|
ddf45bf7a7 | ||
|
|
9fabeac7e1 | ||
|
|
758b1f9365 | ||
|
|
0050d34d0e | ||
|
|
fd547383c4 | ||
|
|
edb640f496 | ||
|
|
4569fa3e86 | ||
|
|
33af012352 | ||
|
|
5cc6688d9c | ||
|
|
f8a5f610bb | ||
|
|
877ac8adc7 | ||
|
|
39040c263e | ||
|
|
08759762a1 | ||
|
|
7897fbcf36 | ||
|
|
ce93bb1afb | ||
|
|
3e8085f460 | ||
|
|
2ab201d278 | ||
|
|
ccdac844b9 | ||
|
|
4474939c51 | ||
|
|
f32975ccb0 | ||
|
|
4f92b22955 | ||
|
|
1d8ac0ab45 | ||
|
|
4cfc5258b2 | ||
|
|
14b7415005 | ||
|
|
f482b712ca | ||
|
|
c40a5cbaaa | ||
|
|
9d0efca1e5 | ||
|
|
cf4d76e9a4 | ||
|
|
56569509f9 | ||
|
|
a0904be305 | ||
|
|
7d156fb47b | ||
|
|
e1796d24ad | ||
|
|
2899b1f8eb | ||
|
|
335a033091 | ||
|
|
80aa2367b0 | ||
|
|
e6ca5884a3 | ||
|
|
ef52c84e25 | ||
|
|
478f2fc9ae | ||
|
|
98afededa6 | ||
|
|
7ad2ca9f16 | ||
|
|
03ab379c63 | ||
|
|
bd71efd22b | ||
|
|
87b7a1c759 | ||
|
|
5c59c3b1b8 | ||
|
|
bb98db4568 | ||
|
|
80f0afd600 | ||
|
|
5bc37b51bb | ||
|
|
0f68892d11 | ||
|
|
37e731c2fd | ||
|
|
8312ae30f1 | ||
|
|
28ec42dddf | ||
|
|
d9f572188e | ||
|
|
720a95d472 | ||
|
|
74ba60f4f1 | ||
|
|
e717473a21 | ||
|
|
aca1e26c3b | ||
|
|
0b1bcebc72 | ||
|
|
058a5dca17 | ||
|
|
da21131f9c | ||
|
|
30fd9f84e0 | ||
|
|
291f881e61 | ||
|
|
bdb465b54e | ||
|
|
e39a10da22 | ||
|
|
8f4ab6a7f1 | ||
|
|
3259fc1a52 | ||
|
|
05d85a0dc1 | ||
|
|
e7c4658805 | ||
|
|
51220bcae6 | ||
|
|
e8ff42dd75 | ||
|
|
6bd54ccf2b | ||
|
|
c3bcaba61e | ||
|
|
14693b4a6e | ||
|
|
b1a75df507 | ||
|
|
42d65cf0dd | ||
|
|
b18803e343 | ||
|
|
a36a071dbe | ||
|
|
edc95e1fa0 | ||
|
|
11b3d3ef41 | ||
|
|
64f339b1fa | ||
|
|
a6eefa7afd | ||
|
|
837d1fb43d | ||
|
|
0d190b4ade | ||
|
|
fbc7acea60 | ||
|
|
e6676e1a05 | ||
|
|
b9a56c6c65 | ||
|
|
520c152cc6 | ||
|
|
f7ddf6f872 | ||
|
|
e6997d81c1 | ||
|
|
31923fc73c | ||
|
|
5dbfc97780 | ||
|
|
da525fa1c8 | ||
|
|
372866ee95 | ||
|
|
d1e1df01c0 | ||
|
|
b12d9f352b | ||
|
|
b3dc4b617b | ||
|
|
67e3ff368d | ||
|
|
569ad41894 | ||
|
|
bd5225967d | ||
|
|
9c9df4ee3b | ||
|
|
a859a20062 | ||
|
|
4fd8e736f9 | ||
|
|
41fc9b329b | ||
|
|
3ea9408103 | ||
|
|
589170ac8a | ||
|
|
54bd6cd402 | ||
|
|
9374b56ec7 | ||
|
|
f6a3ea79ef | ||
|
|
840e2610c5 | ||
|
|
d673dbf1cc | ||
|
|
26c5de3122 | ||
|
|
7c5d2647c8 | ||
|
|
3a6256179c | ||
|
|
1d4e5fbd70 | ||
|
|
a456fffe45 | ||
|
|
3240924b2c | ||
|
|
670406991c | ||
|
|
839543bbdd | ||
|
|
47511b33a3 | ||
|
|
264fd0b3a4 | ||
|
|
f10af90c14 | ||
|
|
782a82ea30 | ||
|
|
bc0d6bb09e | ||
|
|
54fd9d9492 | ||
|
|
59f804c96a | ||
|
|
fba1325abe | ||
|
|
4b634ad67a | ||
|
|
3990ba055a | ||
|
|
48784a3d7a | ||
|
|
61e87718da | ||
|
|
a55dd34eda | ||
|
|
fc304e5dc7 | ||
|
|
3870950e29 | ||
|
|
fc97830d22 | ||
|
|
c7579c259b | ||
|
|
1272e09a73 | ||
|
|
79b4cbe7f1 | ||
|
|
9a000f7881 | ||
|
|
30367ad328 | ||
|
|
d67b447895 | ||
|
|
0d9c727dde | ||
|
|
321a56d5cf | ||
|
|
fbbc4ee864 | ||
|
|
36f6e792db | ||
|
|
f71c7c5076 | ||
|
|
33ec5a5cce | ||
|
|
bbcf0add3f | ||
|
|
80aa041d1c | ||
|
|
35617e97a5 | ||
|
|
9a802c0db9 | ||
|
|
d588f2fdee | ||
|
|
ad0424175d | ||
|
|
3bf9856c26 | ||
|
|
2395ee05f9 | ||
|
|
2a5168e800 | ||
|
|
da861cc73c | ||
|
|
4cd974cb0e | ||
|
|
14cb32fc58 | ||
|
|
3197671950 | ||
|
|
cd29baf9cc | ||
|
|
03ca76e679 | ||
|
|
933855baac | ||
|
|
ee61259114 | ||
|
|
d26f88e0f1 | ||
|
|
c8fc144b8a | ||
|
|
d8ae647140 | ||
|
|
ab5767bee8 | ||
|
|
7c63d3c176 | ||
|
|
d837dfdd77 | ||
|
|
31612a7cb1 | ||
|
|
3d25ec32c0 | ||
|
|
e3cda07f0f | ||
|
|
18762961e1 | ||
|
|
9cc1112a3e | ||
|
|
2204a8437f | ||
|
|
c37e0d2fab | ||
|
|
4ce85692a7 | ||
|
|
860658f367 | ||
|
|
4310ce95ea | ||
|
|
66ae6b2a33 | ||
|
|
5f45a38f08 | ||
|
|
c26cd30dfd | ||
|
|
4b0fe94f34 | ||
|
|
7fc80f8e10 | ||
|
|
2875676308 | ||
|
|
814726d545 | ||
|
|
0ae60a0463 | ||
|
|
c96bef9ac5 | ||
|
|
db9fd9e4d5 | ||
|
|
90ab2f0dbd | ||
|
|
f8f6cb3cc2 | ||
|
|
72d741f795 | ||
|
|
e784f4bcf0 | ||
|
|
a50f34d2b4 | ||
|
|
dce58a7793 | ||
|
|
5c9de8de7c | ||
|
|
c1c2ceb33e | ||
|
|
b39eca0e0e | ||
|
|
cadcc69eef | ||
|
|
120f2b2d44 | ||
|
|
495d7ecf19 | ||
|
|
140a45a7cb | ||
|
|
bd12ff73f8 | ||
|
|
7b4182dce3 | ||
|
|
f8098423a3 | ||
|
|
9e1a8c29ac | ||
|
|
157833d806 | ||
|
|
91a61eb014 | ||
|
|
1f037dbda7 | ||
|
|
d9c68e7360 | ||
|
|
51507d3905 | ||
|
|
31e83ebbb5 | ||
|
|
3e2f1fb5a8 | ||
|
|
5ad360eaf2 | ||
|
|
b7b8cd3fea | ||
|
|
243fccac9e | ||
|
|
51b3035434 | ||
|
|
be9fba23ac | ||
|
|
0ac9b703d5 | ||
|
|
21c51ff074 | ||
|
|
a735b2f0b0 | ||
|
|
0baea2c5ea | ||
|
|
ca2463567e | ||
|
|
c950dc5f53 | ||
|
|
5c6a29f2d7 | ||
|
|
6e12a9d22e | ||
|
|
df819853ef | ||
|
|
d8c3fb4190 | ||
|
|
25e5ecfe61 | ||
|
|
809b5bbe18 | ||
|
|
1ec9139307 | ||
|
|
293ea1f6cf | ||
|
|
ed43a47ad7 | ||
|
|
82980639c8 | ||
|
|
01dd493475 | ||
|
|
0b29c8c51b | ||
|
|
7c2f4277c3 | ||
|
|
8c515f542c | ||
|
|
73d80b136f | ||
|
|
93c5cf70a0 | ||
|
|
e2a098c3ff | ||
|
|
c766048224 | ||
|
|
9eb87fae62 | ||
|
|
7f2f4d8e3b | ||
|
|
ce5f9c0f08 | ||
|
|
cf443ed217 | ||
|
|
bde582d8ff | ||
|
|
0a00332580 | ||
|
|
137d7bf240 | ||
|
|
302c7ef455 | ||
|
|
87b54a44c0 | ||
|
|
f9eee0de93 | ||
|
|
1114d2d30d | ||
|
|
65d50d046c | ||
|
|
b2ef957682 | ||
|
|
56be23e304 | ||
|
|
215869037b | ||
|
|
f5d4581911 | ||
|
|
5848efdf67 | ||
|
|
c79277afaf | ||
|
|
59c8e1ae1c | ||
|
|
d50678649a | ||
|
|
c19f440094 | ||
|
|
b60160f2b9 | ||
|
|
e54c4ed435 | ||
|
|
749774cd03 | ||
|
|
7cc609fdc3 | ||
|
|
2cc8bed203 | ||
|
|
175f02a1c3 | ||
|
|
8a8ac0ccde | ||
|
|
168427f24e | ||
|
|
8338518405 | ||
|
|
95783b7ff3 | ||
|
|
f7a4f956d7 | ||
|
|
feea4d4785 | ||
|
|
e267623025 | ||
|
|
5c22583a52 | ||
|
|
7ba93c9742 | ||
|
|
b4b40bf82f | ||
|
|
b96218f305 | ||
|
|
e4013a616f | ||
|
|
6f3ab8f974 | ||
|
|
c47fc61b73 | ||
|
|
01042bbc54 | ||
|
|
e75f8b75c5 | ||
|
|
ae5e77925a | ||
|
|
b1f2a0e057 | ||
|
|
6f61d5aa11 | ||
|
|
9562f06fea | ||
|
|
c6eaf4ae03 | ||
|
|
24c994dfb5 | ||
|
|
f623e5d004 | ||
|
|
9ac03a00d8 | ||
|
|
52b819a1b7 | ||
|
|
3ad32e0fc7 | ||
|
|
18c4a55518 | ||
|
|
0d8a889556 | ||
|
|
25f180a297 | ||
|
|
ae2f2d8294 | ||
|
|
d3ac95d1f6 | ||
|
|
a138c4a1c7 | ||
|
|
0f359e84d6 | ||
|
|
49d73b7d4f | ||
|
|
33dc7b82ea | ||
|
|
4714ea6037 | ||
|
|
c2e5f241b0 | ||
|
|
7f0e299b74 | ||
|
|
e418ccf8e0 | ||
|
|
53e119c0ce | ||
|
|
f934d3b7b1 | ||
|
|
12d5b9f6f6 | ||
|
|
727a5cfbf5 | ||
|
|
6345080769 | ||
|
|
928f3d32cd | ||
|
|
81fd2dc82f | ||
|
|
225918ac52 | ||
|
|
796343bc5e | ||
|
|
cdb86f6c11 | ||
|
|
625e26f01f | ||
|
|
21f0429fd6 | ||
|
|
e7a30048cf | ||
|
|
ed952e19eb | ||
|
|
e4a7bdd0eb | ||
|
|
13e394bbfe | ||
|
|
a6b95064d9 | ||
|
|
c30a556bdc | ||
|
|
2bebba740e | ||
|
|
57dfb02689 | ||
|
|
7931d8b26e | ||
|
|
434d50b2d1 | ||
|
|
fad905a426 | ||
|
|
475c3b690b | ||
|
|
88eae3cb9a | ||
|
|
0021c7233c | ||
|
|
725224b6c7 | ||
|
|
28f399079d | ||
|
|
3e0d594e29 | ||
|
|
9092b1f9b0 | ||
|
|
4ac30ff7b2 | ||
|
|
803d3395d6 | ||
|
|
51288231cd | ||
|
|
a44cd528ef | ||
|
|
cfbe0b031e | ||
|
|
bf7fb1620e | ||
|
|
50056fd301 | ||
|
|
927b134996 | ||
|
|
9d66619cda | ||
|
|
c9670536cc | ||
|
|
731518a4fc | ||
|
|
764df316a7 | ||
|
|
1ecfdafbe8 | ||
|
|
4b09aeefd6 | ||
|
|
71e407c3a5 | ||
|
|
48c4e7432d | ||
|
|
202194c16f | ||
|
|
0af5713483 | ||
|
|
3b6a5c353c | ||
|
|
e6d8f34db0 | ||
|
|
ef97a0d184 | ||
|
|
40866754d1 | ||
|
|
d5fb7d094c | ||
|
|
48b861054c | ||
|
|
568b7541a4 | ||
|
|
914a6e6a57 | ||
|
|
d1e737dc96 | ||
|
|
ae68a05f87 | ||
|
|
79e6f3ef1c | ||
|
|
5f137f4055 | ||
|
|
4f5215a312 | ||
|
|
06ea8f6cb1 | ||
|
|
2e2890d8d3 | ||
|
|
a07ef6c273 | ||
|
|
a02acc9c67 | ||
|
|
bc5e61b04b | ||
|
|
665036189d | ||
|
|
8fe72e7f2e | ||
|
|
4a373015de | ||
|
|
31ad540286 | ||
|
|
448faebfad | ||
|
|
3b7f25071e | ||
|
|
c661395bb0 | ||
|
|
c231220b42 | ||
|
|
1c6e5bec73 | ||
|
|
8919aaad94 | ||
|
|
ff6a2fe734 | ||
|
|
5ca4f7e411 | ||
|
|
2c105718b0 | ||
|
|
9cc471f66f | ||
|
|
e18867c938 | ||
|
|
27bd942c4c | ||
|
|
234ca72e73 | ||
|
|
f35a09a34d | ||
|
|
fa6d06a4ac | ||
|
|
ec6daeeadb | ||
|
|
f24f25f071 | ||
|
|
dbf0e3af9f | ||
|
|
6ecb607c0b | ||
|
|
1bf6b00be0 | ||
|
|
4b4f52e39a | ||
|
|
1170efbe02 | ||
|
|
3e86e73fed | ||
|
|
dd43861572 | ||
|
|
61a19ca9e0 | ||
|
|
d02af31e7c | ||
|
|
ab7b88b67b | ||
|
|
b80c816c5a | ||
|
|
c0ad6f49e7 | ||
|
|
91a56a8c2c | ||
|
|
135a473dc5 | ||
|
|
a8b53b07e4 | ||
|
|
171cbd4497 | ||
|
|
a1383612bc | ||
|
|
fe798c19f3 | ||
|
|
ead3add4cc | ||
|
|
db13c2d1c5 | ||
|
|
f2daab4b80 | ||
|
|
6fd02c83ab | ||
|
|
c288a0503a | ||
|
|
2e2b65a4bb | ||
|
|
982d0a53f9 | ||
|
|
f89ecfb595 | ||
|
|
bddb96a5fa | ||
|
|
39b29d12e4 | ||
|
|
6cb1e2c06d | ||
|
|
d932a9a862 | ||
|
|
341b552d0b | ||
|
|
1586bd9768 | ||
|
|
e8dde3a756 | ||
|
|
bb591bf56d | ||
|
|
8cf83b276a | ||
|
|
a1547a7288 | ||
|
|
0722403a23 | ||
|
|
cd4a91d975 | ||
|
|
c7870b4e9d | ||
|
|
239dae5a65 | ||
|
|
13c89d18d7 | ||
|
|
c5dc42eafe | ||
|
|
59b3faab1e | ||
|
|
b88beb5002 | ||
|
|
9fea35c25d | ||
|
|
372013bcc6 | ||
|
|
20ca58b570 | ||
|
|
9c4cca20fb | ||
|
|
6b8536a86e | ||
|
|
3b41857637 | ||
|
|
fa78ac2157 | ||
|
|
0a0bb9c83c | ||
|
|
36834a039e | ||
|
|
4960c898bb | ||
|
|
af61edbc5c | ||
|
|
237bfc08a1 | ||
|
|
032e9a089d | ||
|
|
ba83779dde | ||
|
|
267d72f844 | ||
|
|
b533cdb89f | ||
|
|
3f118ac76b | ||
|
|
08a8f0097c | ||
|
|
c7a3599e46 | ||
|
|
62f80baa69 | ||
|
|
09dc8cd437 | ||
|
|
c613ba401b | ||
|
|
866df0b5cf | ||
|
|
8f2b56ba05 | ||
|
|
2049885f11 | ||
|
|
4caef337d5 | ||
|
|
a644792bf6 | ||
|
|
54c7f8fc6a | ||
|
|
2e59b87bbe | ||
|
|
f89242ad8f | ||
|
|
845903788c | ||
|
|
53a9047c20 | ||
|
|
a2b74bbc30 | ||
|
|
50ef93cc45 | ||
|
|
e2a5da9c14 | ||
|
|
bf31116d0d | ||
|
|
d79fa8c4f3 | ||
|
|
87e0a7bea3 | ||
|
|
b34cabd55c | ||
|
|
704bd36c77 | ||
|
|
5c2c4d0f4b | ||
|
|
80cca5cb34 | ||
|
|
dcd603f2f5 | ||
|
|
1bee3b49bf | ||
|
|
11bcccb8f9 | ||
|
|
7425493706 | ||
|
|
dca0cfd424 | ||
|
|
21314def89 | ||
|
|
90e544d279 | ||
|
|
89e21558a1 | ||
|
|
b28cc51457 | ||
|
|
2d0a203b5f | ||
|
|
2948666c1d | ||
|
|
b2a2659546 | ||
|
|
e2eb7102c9 | ||
|
|
56301e653b | ||
|
|
1d825e08d2 | ||
|
|
c81a27709c | ||
|
|
2b143172f8 | ||
|
|
84330ec6eb | ||
|
|
0bd703de7f | ||
|
|
e6a55b7d5c | ||
|
|
b6ec7631d2 | ||
|
|
5abbd00d63 | ||
|
|
515c94976e | ||
|
|
912adad8f6 | ||
|
|
70fdd193a3 | ||
|
|
9d83e968fc | ||
|
|
04329da133 | ||
|
|
19ccfbb1a6 | ||
|
|
c53ab09abc | ||
|
|
a4d8daac1d | ||
|
|
99d566d521 | ||
|
|
aa37f73161 | ||
|
|
087518378d | ||
|
|
913d27929b | ||
|
|
530d39bec5 | ||
|
|
cf70dc3845 | ||
|
|
d1421fee3f | ||
|
|
3ec490b36e | ||
|
|
ee42685179 | ||
|
|
e02d687771 | ||
|
|
dddb592253 | ||
|
|
d0ad88d317 | ||
|
|
3d3ce0151f | ||
|
|
f017b0e562 | ||
|
|
4ba2bb669b | ||
|
|
9fc2424736 | ||
|
|
980c01ea70 | ||
|
|
758edcaf5c | ||
|
|
950f825e31 | ||
|
|
0a985f5c78 | ||
|
|
58353f4e6d | ||
|
|
c3d2b5c4b0 | ||
|
|
c2764b4b8a | ||
|
|
9bc8ad0d23 | ||
|
|
488c3540a8 | ||
|
|
1d03950a64 | ||
|
|
e24ea7df2e | ||
|
|
fe5e3d6405 | ||
|
|
db8584e40b | ||
|
|
2e4db38d1a | ||
|
|
f82470c1b1 | ||
|
|
650cc93fc0 | ||
|
|
e61070307a | ||
|
|
2fa46867e5 | ||
|
|
636b2e760a | ||
|
|
8cecf46310 | ||
|
|
a7bcda7eba | ||
|
|
a610dd2c70 | ||
|
|
df98c77a48 | ||
|
|
331dc33c86 | ||
|
|
8493b37171 | ||
|
|
2449c46165 | ||
|
|
a642c2d344 | ||
|
|
88c7b988e7 | ||
|
|
36651260c6 | ||
|
|
6c0072dc54 | ||
|
|
e38f5a77c0 | ||
|
|
6443926a79 | ||
|
|
489deb9ee6 | ||
|
|
a2039110e0 | ||
|
|
f1666b64ee | ||
|
|
d7e419c88b | ||
|
|
de8f8053be | ||
|
|
7aadef5c21 | ||
|
|
f8384dfbdf | ||
|
|
b91cbf827c | ||
|
|
096619d9f7 | ||
|
|
7b8006406c | ||
|
|
90483d549a | ||
|
|
5473055c1c | ||
|
|
f3b082b8e1 | ||
|
|
52fe99a080 | ||
|
|
68b1e941d1 | ||
|
|
109fce9b1a | ||
|
|
014d73dd3f | ||
|
|
ad82744e91 | ||
|
|
638f66ade4 | ||
|
|
c190efa7c4 | ||
|
|
b071eb3366 | ||
|
|
a7c7a86a90 | ||
|
|
0c21519cf6 | ||
|
|
d7f805b26e | ||
|
|
4f702feab3 | ||
|
|
46175ceb1f | ||
|
|
c206860736 | ||
|
|
b97c222490 | ||
|
|
53ad5e3cec | ||
|
|
e67ce8dbf4 | ||
|
|
ac661be5c4 | ||
|
|
ab4e386491 | ||
|
|
aa125b535e | ||
|
|
29df3bb5e2 | ||
|
|
80ee30a532 | ||
|
|
a20b1bcd89 | ||
|
|
4269abd2ca | ||
|
|
a73397065c | ||
|
|
5805a6c5a3 | ||
|
|
e813077c92 | ||
|
|
b1ae68e8c2 | ||
|
|
b6162b2d4a | ||
|
|
0da7854f7f | ||
|
|
a073747e66 | ||
|
|
0975e31371 | ||
|
|
a1231f1708 | ||
|
|
3581da0fe7 | ||
|
|
531810e633 | ||
|
|
36a1db78e2 | ||
|
|
0e58009ced | ||
|
|
ecdcc6894b | ||
|
|
677a2f9396 | ||
|
|
b14f30d4d1 | ||
|
|
4396df55ae | ||
|
|
de6ce93a31 | ||
|
|
b449b7cdfc | ||
|
|
de4412fcff | ||
|
|
8e9b34125d | ||
|
|
024b3175fa | ||
|
|
9fadc6df3b | ||
|
|
f51f8f0f2d | ||
|
|
d805df4887 | ||
|
|
d161c6c074 | ||
|
|
6603c9e682 | ||
|
|
157360c26c | ||
|
|
e16199b360 | ||
|
|
df42424970 | ||
|
|
22ce6ce75e | ||
|
|
e8dde142d2 | ||
|
|
44fe272ce5 | ||
|
|
23a8f32910 | ||
|
|
d523c0408e | ||
|
|
0c2edba316 | ||
|
|
7cfc643522 | ||
|
|
98b446efd2 | ||
|
|
bbf9aaed64 | ||
|
|
13d36457af | ||
|
|
4f850b0f2e | ||
|
|
7118e0d9d3 | ||
|
|
900edef407 | ||
|
|
a7e4b99ce6 | ||
|
|
0463667270 | ||
|
|
d179bc20ae | ||
|
|
bc658a9223 | ||
|
|
e32e3baf3a | ||
|
|
4436f4cf42 | ||
|
|
1dc7b40ee9 | ||
|
|
fb2221d281 | ||
|
|
b5a295970b | ||
|
|
485a453b27 | ||
|
|
03d9ba9579 | ||
|
|
ead7adea31 | ||
|
|
9793fdfc06 | ||
|
|
60b73e5c0d | ||
|
|
ed374c83d1 | ||
|
|
f53828e01a | ||
|
|
91ab50fa6a | ||
|
|
bee0d9d086 | ||
|
|
b38f09f118 | ||
|
|
65f57e8bf1 | ||
|
|
e424e42e05 | ||
|
|
a22cd5338d | ||
|
|
f921737d1c | ||
|
|
2f760afc70 | ||
|
|
4ce56433f9 | ||
|
|
f8ace5fbb5 | ||
|
|
f89778e969 | ||
|
|
99f74d3824 | ||
|
|
dec9c42edc | ||
|
|
34365c13cb | ||
|
|
8a4c780fb1 | ||
|
|
bed49408da | ||
|
|
5dd400bdc5 | ||
|
|
07d5a03a95 | ||
|
|
d53e7665d7 | ||
|
|
4021d7cbac | ||
|
|
2634ca5f28 | ||
|
|
aa887fe27b | ||
|
|
c9077e1254 | ||
|
|
fcf5636d9a | ||
|
|
1c4713ef1a | ||
|
|
d48da82f55 | ||
|
|
7128ab26d8 | ||
|
|
58155041d1 | ||
|
|
e96453eac9 | ||
|
|
a1d98c54ad | ||
|
|
fd423481cb | ||
|
|
48da7d0c2e | ||
|
|
949577fb07 | ||
|
|
dc876efff8 | ||
|
|
c39b650e9b | ||
|
|
12c1cb871d | ||
|
|
c75e86812d | ||
|
|
cc1fffbe8b | ||
|
|
e6313a5e52 | ||
|
|
2a278dcff9 | ||
|
|
8539529061 | ||
|
|
5a4b7d93c0 | ||
|
|
5e459effc0 | ||
|
|
5f0cf2ecb5 | ||
|
|
44c133a68c | ||
|
|
f842f15cd4 | ||
|
|
04c78a37d7 | ||
|
|
cba4dbdf84 | ||
|
|
2fbb162bbf | ||
|
|
a1be962997 | ||
|
|
a3c3f5d990 | ||
|
|
c785d4fdca | ||
|
|
6b78707bd8 | ||
|
|
a1565ed481 | ||
|
|
ae056b312f | ||
|
|
b20c1158b0 | ||
|
|
210f567b74 | ||
|
|
82b91c7bd9 | ||
|
|
5168a99c3b | ||
|
|
e437917841 | ||
|
|
6543440926 | ||
|
|
30c53c6048 | ||
|
|
87412eb4ff | ||
|
|
f1ad5fc57d | ||
|
|
dd3a457ec0 | ||
|
|
069f2aba8b | ||
|
|
9e19553150 | ||
|
|
2d7b009442 | ||
|
|
68817e3595 | ||
|
|
3e229d6b82 | ||
|
|
5fce471577 | ||
|
|
68b1e7cb16 | ||
|
|
761e02ff6e | ||
|
|
5fe4408631 | ||
|
|
bae0685ac1 | ||
|
|
6322a0c045 | ||
|
|
0b89de905e | ||
|
|
49c4453678 | ||
|
|
c056cafda6 | ||
|
|
b375c24340 | ||
|
|
5a76131f24 | ||
|
|
764769a6ca | ||
|
|
b0226df3ab | ||
|
|
8c86120b33 |
No files matched your search
@@ -0,0 +1,7 @@
|
||||
* text=auto
|
||||
|
||||
*.sh text eol=lf
|
||||
gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
|
||||
*.jar binary
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Server version**
|
||||
|
||||
|
||||
**Plugin version** ("latest" is not a version)
|
||||
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
|
||||
**How to reproduce**
|
||||
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
|
||||
**Actual behavior**
|
||||
|
||||
|
||||
**Additional info** (errors, screenshots, ...)
|
||||
@@ -0,0 +1,63 @@
|
||||
name: Bug Report
|
||||
description: Plugin does not work as described.
|
||||
labels: ["Type: Bug"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: How the plugin behaves
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: How the plugin is supposed to behave instead with your configuration.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: What to do in order to reproduce this issue
|
||||
placeholder: |
|
||||
Step 1: ..
|
||||
Step 2: ..
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Anything else you want to provide that will help in resolving the issue faster, such as screenshots or videos.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have read the wiki to make sure it's not an issue with configuration
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
|
||||
required: true
|
||||
@@ -0,0 +1,57 @@
|
||||
name: Error
|
||||
description: Error message with a stack trace in console or error file.
|
||||
labels: ["Type: Error"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Stack trace
|
||||
description: The error message and stack trace you received in console or errors.log file.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce (if known)
|
||||
description: What to do in order to reproduce this issue
|
||||
placeholder: |
|
||||
Step 1: ..
|
||||
Step 2: ..
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Any other info you want to provide.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have included a paste of the error
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
|
||||
required: true
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Memory leak
|
||||
description: Memory leak caused by TAB.
|
||||
labels: ["Type: Memory leak"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Heap dump
|
||||
description: Link to uploaded memory dump which I can look into.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Uncleared object(s)
|
||||
description: Objects you believe should have been removed from the memory but are not
|
||||
placeholder: |
|
||||
There is only 1 player online, but heap dump has 100 instances of TabPlayer
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Any other info you want to provide.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have included download link of my heap dump
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: I have explained what I believe is wrong with the heap dump
|
||||
required: true
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Performance issue
|
||||
description: Performance issue in TAB or in server caused by TAB
|
||||
labels: ["Type: Performance"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Evidence
|
||||
description: Any evidence proving the performance issue, such as output of /tab cpu or timings.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce (if known)
|
||||
description: What to do in order to reproduce this issue
|
||||
placeholder: |
|
||||
Step 1: ..
|
||||
Step 2: ..
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Any other info you want to provide.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have included sufficient evidence of the issue
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Wiki change
|
||||
description: Change to text on a wiki page or adding a new section.
|
||||
labels: ["Type: Wiki change"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Page link
|
||||
description: Link to the page that should receive the change.
|
||||
placeholder: https://github.com/NEZNAMY/TAB/wiki/Installation
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current text (if applicable)
|
||||
description: Current version of the text (if modifying), ignore in case of new section.
|
||||
placeholder: |
|
||||
Installation on Bukkit/Spigot/Paper is as simple as it can be. Just put the plugin into plugins folder and restart the server. You can also run `/plugman load tab` to load the plugin while server is running.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: New text
|
||||
description: What the text should be changed to / how should new section look
|
||||
placeholder: |
|
||||
Put the plugin into plugins folder and restart the server. In case you don't want to restart your server, loading with plugins like PlugMan is supported.
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1,45 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v4
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0 #https://github.com/actions/checkout/releases
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: "${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}"
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Build and analyze
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
run: ./gradlew clean build
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
|
||||
with:
|
||||
name: Jar
|
||||
path: jar/build/libs/TAB-**.jar
|
||||
+10
-6
@@ -1,8 +1,7 @@
|
||||
# Compiled class file
|
||||
*.class
|
||||
/bin/
|
||||
/target/
|
||||
/src/test/
|
||||
bin/
|
||||
target/
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
@@ -31,8 +30,13 @@ hs_err_pid*
|
||||
.project
|
||||
|
||||
# intellij
|
||||
/.idea/
|
||||
.idea/
|
||||
|
||||
# other
|
||||
.gitattributes
|
||||
*.iml
|
||||
*.iml
|
||||
run/
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -0,0 +1,10 @@
|
||||
If this plugin has helped you in any way, and you would like to return something back
|
||||
to make the plugin even better, there is a lot of ways to contribute:
|
||||
* Open a **pull request** containing a new feature or a bug fix, which you believe many users will benefit from.
|
||||
* Make detailed high-quality **bug reports**. The difference between a bug getting fixed in 1 week vs 1 hour
|
||||
is in quality of the report (typically providing correct steps to reproduce that actually work).
|
||||
* Test **experimental builds**, which can be found in [GitHub Actions](https://github.com/NEZNAMY/TAB/actions) for problems of any kind including bugs.
|
||||
This way you will help make the actual releases more stable / help get stable releases sooner.
|
||||
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
|
||||
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples 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.
|
||||
@@ -1,4 +1,4 @@
|
||||
Apache License
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
@@ -172,30 +172,3 @@ Apache License
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,49 +1,70 @@
|
||||
## Introduction
|
||||
Here it is. After countless requests to go opensource, I did it to make those people quiet. It worked.
|
||||
It's already been a year and I still can't see any positives of it. This way I just made it easier for everyone to make a plugin as good, so people can stop using it and switch to something better.
|
||||
|
||||
|
||||
## Compiling
|
||||
Plugin can be compiled using maven.
|
||||
# About
|
||||
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
|
||||
similar plugins in terms of features, performance, compatibility and support.
|
||||
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
|
||||
|
||||
|
||||
## Premium code
|
||||
The source contains content of premium version as well. However, this must be activated by setting [this line to return true](https://github.com/NEZNAMY/TAB/blob/master/src/main/java/me/neznamy/tab/shared/TAB.java#L79).
|
||||
|
||||
|
||||
## Links
|
||||
Free version
|
||||
Spigot (releases): https://www.spigotmc.org/resources/57806/
|
||||
Pre-releases https://github.com/NEZNAMY/TAB/releases
|
||||
Premium version
|
||||
Polymart (releases): https://polymart.org/resource/484
|
||||
MC-Market (releases): https://www.mc-market.org/resources/14009/
|
||||
Pre-releases on discord for verified buyers
|
||||
Bukkit bridge https://www.spigotmc.org/resources/83966/
|
||||
Wiki https://github.com/NEZNAMY/TAB/wiki
|
||||
Discord invite link: https://discord.gg/EaSvdk6
|
||||
|
||||
|
||||
## Downloads
|
||||
Official release: https://www.spigotmc.org/resources/57806/
|
||||
Pre-releases: https://github.com/NEZNAMY/TAB/releases
|
||||
|
||||
|
||||
## Maven Repository
|
||||
```
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
# 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)).
|
||||
|
||||
<dependencies>
|
||||
<!-- TAB -->
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>2.8.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
# Compiling
|
||||
Compilation requires JDK 17 and up.
|
||||
To compile the plugin, run `./gradlew build` from the terminal.
|
||||
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
|
||||
The universal jar contains all modules for all supported platforms.
|
||||
|
||||
# Customer support
|
||||
Unlike with other projects where requirements and level of support provided are unclear,
|
||||
we provide a clear answer to what kind of service you will or will not receive. Some projects don't
|
||||
even clearly state if they are free or paid. While they are listed as paid on spigot, they have
|
||||
a free download link in description, which can be confusing.
|
||||
Support is also quite often "we have a discord server, but don't ping devs for support", suggesting
|
||||
there is no official support, just the community.
|
||||
|
||||
In TAB, everything is clearly defined. **Downloads are free, but services are paid.**
|
||||
The plugin is free to download for everyone. The only exception might be occasional update delays
|
||||
for free users once a year.
|
||||
Customer support, however, is only provided to customers who have formally purchased a copy
|
||||
of the plugin on any of the websites the plugin is being sold on:
|
||||
* [BuiltByBit](https://builtbybit.com/resources/14009/)
|
||||
* [Polymart](https://polymart.org/resource/484)
|
||||
|
||||
After purchasing, you can request assistance via either private messages on the website where you
|
||||
have purchased it, or using [TAB's Discord server](https://discord.gg/EaSvdk6) (which is highly
|
||||
preferred, since the communication will be much faster thanks to notifications and more).
|
||||
* We use a ticket system, which allows us to effectively track any amount of unresolved problems.
|
||||
No more questions getting lost in the sea of messages from other people asking. Tickets are only closed
|
||||
after user confirmed all of their questions were answered and then receive a transcript of the ticket
|
||||
from the bot after ticket is closed into private messages (unless user is blocking them).
|
||||
* We do not rely on the community helping, which often makes things even worse. We know people have
|
||||
better things to do than helping random people with a random plugin, especially when most people only
|
||||
have a very basic knowledge of the plugin, not being able to answer more complicated problems.
|
||||
* We have notifications enabled and reply as soon as possible. We do not have the server muted unlike
|
||||
most mods on most servers who only check it when they are really bored, give a wiki link and
|
||||
disappear again.
|
||||
* As the developer, I do not rely on helpers providing support instead of me. They are handy as
|
||||
I am obviously not available 24/7, but I do not intentionally leave anything to them. I reply
|
||||
whenever I am available without any intentional delays.
|
||||
|
||||
# Other links
|
||||
Download the plugin for free:
|
||||
* [GitHub releases](https://github.com/NEZNAMY/TAB/releases)
|
||||
* [BuiltByBit](https://builtbybit.com/resources/20631/)
|
||||
* [Modrinth](https://modrinth.com/plugin/tab-was-taken)
|
||||
|
||||
[Wiki](https://github.com/NEZNAMY/TAB/wiki)
|
||||
|
||||
# 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.**
|
||||
|
||||
# Contributing
|
||||
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
|
||||
@@ -0,0 +1,2 @@
|
||||
# Security Policy
|
||||
If you found a security issue, send me a private message anywhere.
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies {
|
||||
compileOnlyApi("org.jetbrains:annotations:24.0.1")
|
||||
}
|
||||
|
||||
tasks.javadoc {
|
||||
enabled = project.hasProperty("enable-javadoc")
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.api.bossbar.BossBarManager;
|
||||
import me.neznamy.tab.api.event.EventBus;
|
||||
import me.neznamy.tab.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
|
||||
import me.neznamy.tab.api.tablist.SortingManager;
|
||||
import me.neznamy.tab.api.tablist.HeaderFooterManager;
|
||||
import me.neznamy.tab.api.tablist.TabListFormatManager;
|
||||
import me.neznamy.tab.api.nametag.NameTagManager;
|
||||
import me.neznamy.tab.api.tablist.layout.LayoutManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The primary API class to get instances of other API classes
|
||||
*/
|
||||
public abstract class TabAPI {
|
||||
|
||||
/** Instance of the API */
|
||||
@Setter private static TabAPI instance;
|
||||
|
||||
/**
|
||||
* Returns API instance. If instance was not set by the plugin, throws
|
||||
* {@code IllegalStateException}. This is usually caused by shading the API
|
||||
* into own project, which is not allowed. Another option is calling the method
|
||||
* before plugin was able to load.
|
||||
*
|
||||
* @return API instance
|
||||
* @throws IllegalStateException
|
||||
* If instance is {@code null}
|
||||
*/
|
||||
public static @NotNull TabAPI getInstance() {
|
||||
if (instance == null) throw new IllegalStateException("The API instance is null. This can have 2 possible causes: \n" +
|
||||
"#1 - API was called before TAB was loaded. This means your plugin was loaded before TAB was. To make sure your " +
|
||||
"plugin loads after TAB, add it as a depend or soft depend of your plugin.\n" +
|
||||
"#2 - You shaded TAB's classes into your plugin, instead of only using them. This is not allowed. To verify this " +
|
||||
"is your case, unzip your plugin and check for TAB's classes. If they are there, you will need to fix your compiler " +
|
||||
"to not include them, such as scope provided for maven compilation.");
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player object from given UUID
|
||||
*
|
||||
* @param id
|
||||
* Player UUID
|
||||
* @return player object from given UUID
|
||||
*/
|
||||
public abstract @Nullable TabPlayer getPlayer(@NonNull UUID id);
|
||||
|
||||
/**
|
||||
* Returns player object from given name
|
||||
*
|
||||
* @param name
|
||||
* Player name
|
||||
* @return player object from given name
|
||||
*/
|
||||
public abstract @Nullable TabPlayer getPlayer(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Returns collection of all online players. Will return empty list if plugin is disabled (due to a broken configuration file for example).
|
||||
*
|
||||
* @return collection of online players
|
||||
*/
|
||||
public abstract @NotNull TabPlayer[] getOnlinePlayers();
|
||||
|
||||
/**
|
||||
* Return BossBar manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return BossBar manager
|
||||
*/
|
||||
public abstract @Nullable BossBarManager getBossBarManager();
|
||||
|
||||
/**
|
||||
* Returns scoreboard manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return scoreboard manager
|
||||
*/
|
||||
public abstract @Nullable ScoreboardManager getScoreboardManager();
|
||||
|
||||
/**
|
||||
* Returns team manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return team manager
|
||||
*/
|
||||
public abstract @Nullable NameTagManager getNameTagManager();
|
||||
|
||||
/**
|
||||
* Returns header/footer manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return Header/footer manager
|
||||
*/
|
||||
public abstract @Nullable HeaderFooterManager getHeaderFooterManager();
|
||||
|
||||
/**
|
||||
* Returns PlaceholderManager instance
|
||||
*
|
||||
* @return PlaceholderManager instance
|
||||
*/
|
||||
public abstract @NotNull PlaceholderManager getPlaceholderManager();
|
||||
|
||||
/**
|
||||
* Returns TabList name format manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return TabList name format manager
|
||||
*/
|
||||
public abstract @Nullable TabListFormatManager getTabListFormatManager();
|
||||
|
||||
/**
|
||||
* Returns Layout manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
* @return Layout manager
|
||||
*/
|
||||
public abstract @Nullable LayoutManager getLayoutManager();
|
||||
|
||||
/**
|
||||
* Returns Sorting manager instance if at least one feature capable of sorting players is enabled.
|
||||
* If not, returns {@code null}.
|
||||
*
|
||||
* @return Sorting manager
|
||||
*/
|
||||
public abstract @Nullable SortingManager getSortingManager();
|
||||
|
||||
/**
|
||||
* Gets the event bus for registering listeners for TAB events.
|
||||
*
|
||||
* @return the event bus
|
||||
*/
|
||||
public abstract @Nullable EventBus getEventBus();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* An interface representing a player
|
||||
*/
|
||||
public interface TabPlayer {
|
||||
|
||||
/**
|
||||
* Returns player's name
|
||||
*
|
||||
* @return Player's name
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns player's UUID
|
||||
*
|
||||
* @return Player's UUID
|
||||
*/
|
||||
@NotNull UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Returns platform-specific entity
|
||||
*
|
||||
* @return an instance of bukkit/bungee player depending on platform
|
||||
*/
|
||||
@NotNull Object getPlayer();
|
||||
|
||||
/**
|
||||
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
|
||||
*
|
||||
* @return {@code true} if player is fully loaded, {@code false} otherwise
|
||||
*/
|
||||
boolean isLoaded();
|
||||
|
||||
/**
|
||||
* Returns player's primary permission group. If group has been changed using
|
||||
* {@link #setTemporaryGroup(String)}, returns that value. Otherwise, returns group
|
||||
* detected by standard group assign logic
|
||||
*
|
||||
* @return player's primary permission group
|
||||
*/
|
||||
@NotNull String getGroup();
|
||||
|
||||
/**
|
||||
* Temporarily overrides player's group and applies all changes coming from new group.
|
||||
* This includes all properties and sorting, if used.
|
||||
* Set to {@code null} to reset back.
|
||||
*
|
||||
* @param group
|
||||
* New group to use
|
||||
* @see #hasTemporaryGroup()
|
||||
*/
|
||||
void setTemporaryGroup(@Nullable String group);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a temporary group was applied to the player using {@link #setTemporaryGroup(String)}.
|
||||
* If no group was set, returns {@code false}
|
||||
*
|
||||
* @return {@code true} if group is set, {@code false} if not
|
||||
* @see #setTemporaryGroup(String)
|
||||
*/
|
||||
boolean hasTemporaryGroup();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
/**
|
||||
* An enum representing all available BossBar colors.
|
||||
* Calling ordinal() will return color's network ID.
|
||||
*/
|
||||
public enum BarColor {
|
||||
PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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
|
||||
public enum BarStyle {
|
||||
|
||||
PROGRESS("SOLID"),
|
||||
NOTCHED_6("SEGMENTED_6"),
|
||||
NOTCHED_10("SEGMENTED_10"),
|
||||
NOTCHED_12("SEGMENTED_12"),
|
||||
NOTCHED_20("SEGMENTED_20");
|
||||
|
||||
/** Style's name used in Bukkit API */
|
||||
@Getter private final String bukkitName;
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An interface representing a BossBar line.
|
||||
* <p>
|
||||
* For clients 1.8 and lower it uses wither, which only allows 1 bar
|
||||
* to be displayed at a time. Entity packets are not available on BungeeCord,
|
||||
* where nothing will be displayed for these players.
|
||||
* <p>
|
||||
* For 1.9+ it uses the new BossBar feature, allowing configurable styles
|
||||
* and colors, as well as display several bars at once. Limit of maximum
|
||||
* displayed BossBars depends on client resolution and GUI scale, up to
|
||||
* 9 concurrent BossBars displayed at once.
|
||||
*/
|
||||
public interface BossBar {
|
||||
|
||||
/**
|
||||
* Returns name of this BossBar. If it was defined in config, returns
|
||||
* the name specified in config. If it was made using the API, returns
|
||||
* randomly generated ID given to this BossBar when creating.
|
||||
*
|
||||
* @return name of BossBar
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns randomly generated uuid of this BossBar used internally to match
|
||||
* the BossBar in client with packets or with Bukkit API BossBar instances.
|
||||
*
|
||||
* @return uuid of this BossBar
|
||||
*/
|
||||
@NotNull UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Changes BossBar title to specified string, supporting placeholders.
|
||||
* <p>
|
||||
* If title contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified title is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param title
|
||||
* New title to display in this BossBar
|
||||
*/
|
||||
void setTitle(@NonNull String title);
|
||||
|
||||
/**
|
||||
* Changes BossBar progress to specified string, supporting placeholders.
|
||||
* The string must be a string version of a float value, or a placeholder that
|
||||
* outputs a float value with range 0-100.
|
||||
* <p>
|
||||
* If progress contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified progress is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param progress
|
||||
* New progress to use in this BossBar
|
||||
*/
|
||||
void setProgress(@NonNull String progress);
|
||||
|
||||
/**
|
||||
* Changes BossBar progress to specified value ranging from 0 to 100.
|
||||
* <p>
|
||||
* If specified progress is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param progress
|
||||
* New progress to use in this BossBar
|
||||
*/
|
||||
void setProgress(float progress);
|
||||
|
||||
/**
|
||||
* Changes BossBar color to specified string, supporting placeholders.
|
||||
* The string must be a string version of one of the supported values,
|
||||
* or a placeholder that outputs one of them.
|
||||
* <p>
|
||||
* If color contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified color is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param color
|
||||
* New color to use in this BossBar
|
||||
*/
|
||||
void setColor(@NonNull String color);
|
||||
|
||||
/**
|
||||
* Changes BossBar color to specified enum constant.
|
||||
* <p>
|
||||
* If specified color is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param color
|
||||
* New color to use in this BossBar
|
||||
*/
|
||||
void setColor(@NonNull BarColor color);
|
||||
|
||||
/**
|
||||
* Changes BossBar style to specified string, supporting placeholders.
|
||||
* The string must be a string version of one of the supported values,
|
||||
* or a placeholder that outputs one of them.
|
||||
* <p>
|
||||
* If style contains new placeholders not used before, they are registered using
|
||||
* standard registration logic and refreshed periodically. No need to call
|
||||
* this method to try to keep placeholder values up to date.
|
||||
* <p>
|
||||
* If specified style is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param style
|
||||
* New style to use in this BossBar
|
||||
*/
|
||||
void setStyle(@NonNull String style);
|
||||
|
||||
/**
|
||||
* Changes BossBar style to specified enum constant.
|
||||
* <p>
|
||||
* If specified style is equal to the current one, nothing happens.
|
||||
*
|
||||
* @param style
|
||||
* New style to use in this BossBar
|
||||
*/
|
||||
void setStyle(@NonNull BarStyle style);
|
||||
|
||||
/**
|
||||
* Returns current title of the BossBar in raw format. If it contains placeholders,
|
||||
* their raw identifiers are used in the result.
|
||||
*
|
||||
* @return title of the BossBar
|
||||
*/
|
||||
@NotNull String getTitle();
|
||||
|
||||
/**
|
||||
* Returns progress of the BossBar as a string, which is either entered string
|
||||
* containing placeholders or entered number converted to string
|
||||
*
|
||||
* @return entered progress as a string
|
||||
*/
|
||||
@NotNull String getProgress();
|
||||
|
||||
/**
|
||||
* Returns color of the BossBar as a string, which is either entered string
|
||||
* containing placeholders or entered enum value converted to string
|
||||
*
|
||||
* @return entered color as a string
|
||||
*/
|
||||
@NotNull String getColor();
|
||||
|
||||
/**
|
||||
* Returns style of the BossBar as a string, which is either entered string
|
||||
* containing placeholders or entered enum value converted to string
|
||||
*
|
||||
* @return entered style as a string
|
||||
*/
|
||||
@NotNull String getStyle();
|
||||
|
||||
/**
|
||||
* Registers this BossBar to specified player.
|
||||
* <p>
|
||||
* If the player already sees this BossBar, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* Player to register this BossBar to
|
||||
*/
|
||||
void addPlayer(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Unregisters this BossBar from specified player.
|
||||
* <p>
|
||||
* If the player does not see this BossBar, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* Player to unregister this BossBar from
|
||||
*/
|
||||
void removePlayer(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns list of players who can see this BossBar.
|
||||
* <p>
|
||||
* The returned list can only be used to read. Writing
|
||||
* will not work properly. For adding/removing players see {@link #addPlayer(TabPlayer)}
|
||||
* and {@link #removePlayer(TabPlayer)}.
|
||||
*
|
||||
* @return List of players seeing this BossBar
|
||||
*/
|
||||
@NotNull List<TabPlayer> getPlayers();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this boss bar is configured as announcement-only bar,
|
||||
* {@code false} if not.
|
||||
*
|
||||
* @return {@code true} if bar is announcement only, {@code false} if not
|
||||
*/
|
||||
boolean isAnnouncementBar();
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface allowing work with BossBars such as creating, sending
|
||||
* and toggling.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getBossBarManager()}.
|
||||
* This requires the BossBar feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
public interface BossBarManager {
|
||||
|
||||
/**
|
||||
* Creates BossBar with specified parameters and registers it to BossBar manager,
|
||||
* allowing to retrieve it later.
|
||||
*
|
||||
* @param title
|
||||
* Title to display, supports placeholders
|
||||
* @param progress
|
||||
* Progress to use. Must be between 0 and 100
|
||||
* @param color
|
||||
* Color to use
|
||||
* @param style
|
||||
* Style to use
|
||||
* @return The newly created BossBar
|
||||
*/
|
||||
@NotNull BossBar createBossBar(@NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style);
|
||||
|
||||
/**
|
||||
* Creates BossBar with specified parameters and registers it to BossBar manager,
|
||||
* allowing to retrieve it later.
|
||||
*
|
||||
* @param title
|
||||
* Title to display, supports placeholders
|
||||
* @param progress
|
||||
* Progress to use. Must be a string version of a number, or
|
||||
* a placeholder that returns a number
|
||||
* @param color
|
||||
* Color to use. Must be a string version of one of enum constants, or
|
||||
* a placeholder that returns one of them
|
||||
* @param style
|
||||
* Style to use. Must be a string version of one of enum constants, or
|
||||
* a placeholder that returns one of them
|
||||
* @return The newly created BossBar
|
||||
*/
|
||||
@NotNull BossBar createBossBar(@NonNull String title, @NonNull String progress, @NonNull String color, @NonNull String style);
|
||||
|
||||
/**
|
||||
* Returns registered BossBar with given name. For BossBars from config it is the name
|
||||
* they were created with, for API BossBars their randomly generated name, which can be
|
||||
* retrieved using {@link me.neznamy.tab.api.bossbar.BossBar#getName()}.
|
||||
* Returns {@code null} if no such BossBar exists.
|
||||
*
|
||||
* @param name
|
||||
* Name of registered BossBar
|
||||
* @return BossBar with specified name
|
||||
*/
|
||||
@Nullable BossBar getBossBar(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Returns a map of all registered BossBars. This includes both BossBars from config
|
||||
* and those registered via API.
|
||||
* <p>
|
||||
* Map key is BossBar's name available in {@link me.neznamy.tab.api.bossbar.BossBar#getName()},
|
||||
* map value is the BossBar interface.
|
||||
*
|
||||
* @return all registered BossBars
|
||||
*/
|
||||
@NotNull Map<String, BossBar> getRegisteredBossBars();
|
||||
|
||||
/**
|
||||
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
|
||||
*
|
||||
* @param player
|
||||
* Player to toggle BossBar for
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if toggle message should be sent, {@code false} if not
|
||||
* @see #hasBossBarVisible(TabPlayer)
|
||||
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
void toggleBossBar(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns true if player can see BossBars, false if toggled via command or API.
|
||||
*
|
||||
* @param player
|
||||
* Player to check toggle status of
|
||||
* @return {@code true} if visible, {@code false} if disabled (toggled)
|
||||
* @see #toggleBossBar(TabPlayer, boolean)
|
||||
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
boolean hasBossBarVisible(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sets BossBar visibility of player to set value. If value did not change, nothing happens.
|
||||
* If visibility changed, toggle message is sent if {@code sendToggleMessage} is {@code true}.
|
||||
*
|
||||
* @param player
|
||||
* Player to set BossBar visibility of
|
||||
* @param visible
|
||||
* {@code true} if BossBar should be visible, {@code false} if not
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if toggle message should be sent if value changed, {@code false} if not
|
||||
* @see #toggleBossBar(TabPlayer, boolean)
|
||||
* @see #hasBossBarVisible(TabPlayer)
|
||||
*/
|
||||
void setBossBarVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Temporarily displays registered BossBar to player for specified amount of time in milliseconds.
|
||||
*
|
||||
* @param player
|
||||
* Player to show BossBar to
|
||||
* @param bossBar
|
||||
* Name of registered BossBar to show
|
||||
* @param duration
|
||||
* In milliseconds for how long should BossBar be displayed
|
||||
* @throws IllegalArgumentException
|
||||
* if no BossBar with specified name exists
|
||||
* @see #announceBossBar(String, int)
|
||||
* @see #getAnnouncedBossBars()
|
||||
*/
|
||||
void sendBossBarTemporarily(@NonNull TabPlayer player, @NonNull String bossBar, int duration);
|
||||
|
||||
/**
|
||||
* Temporarily displays registered BossBar to all players for specified amount of time in milliseconds.
|
||||
*
|
||||
* @param bossBar
|
||||
* Name of registered BossBar to show
|
||||
* @param duration
|
||||
* In milliseconds for how long should BossBar be displayed
|
||||
* @throws IllegalArgumentException
|
||||
* if no BossBar with specified name exists
|
||||
* @see #sendBossBarTemporarily(TabPlayer, String, int)
|
||||
* @see #getAnnouncedBossBars()
|
||||
*/
|
||||
void announceBossBar(@NonNull String bossBar, int duration);
|
||||
|
||||
/**
|
||||
* Returns list of BossBars which are currently being announced.
|
||||
*
|
||||
* @return list of currently active BossBar announcements
|
||||
* @see #sendBossBarTemporarily(TabPlayer, String, int)
|
||||
* @see #announceBossBar(String, int)
|
||||
*/
|
||||
@NotNull List<BossBar> getAnnouncedBossBars();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* The event bus. This is used to register event listeners/handlers for
|
||||
* handling events that may be fired by TAB.
|
||||
*/
|
||||
public interface EventBus {
|
||||
|
||||
/**
|
||||
* Scans the given listener for any method that is annotated with
|
||||
* {@link Subscribe} and registers it as a listener to this event bus.
|
||||
*
|
||||
* <p>All methods in this class that are annotated with {@link Subscribe}
|
||||
* must meet the following criteria:
|
||||
* <ul>
|
||||
* <li>It must be <strong>public</strong>.</li>
|
||||
* <li>It must <strong>not</strong> be abstract.</li>
|
||||
* <li>It must have exactly <strong>one</strong> parameter.</li>
|
||||
* <li>Its single parameter must be <strong>an event</strong>.</li>
|
||||
* </ul>
|
||||
* If a method does not meet the above criteria, an exception <strong>will</strong>
|
||||
* be thrown.
|
||||
*
|
||||
* @param listener the listener to register
|
||||
* @throws RuntimeException if a method does not meet the criteria
|
||||
*/
|
||||
void register(@NonNull Object listener);
|
||||
|
||||
/**
|
||||
* Registers the given handler to this event bus.
|
||||
*
|
||||
* @param type the class type of the event
|
||||
* @param handler the handler to register
|
||||
* @param <E> the type of the event
|
||||
*/
|
||||
<E extends TabEvent> void register(@NonNull Class<E> type, @NonNull EventHandler<E> handler);
|
||||
|
||||
/**
|
||||
* Scans the given listener for any method that is annotated with
|
||||
* {@link Subscribe} and unregisters it from this event bus.
|
||||
*
|
||||
* @param listener the listener to unregister
|
||||
*/
|
||||
void unregister(@NonNull Object listener);
|
||||
|
||||
/**
|
||||
* Unregisters the given handler from this event bus.
|
||||
*
|
||||
* @param handler the handler to unregister
|
||||
* @param <E> the type of the event
|
||||
*/
|
||||
<E extends TabEvent> void unregister(@NonNull EventHandler<E> handler);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
/**
|
||||
* A handler for an event. This allows listening for events in a more
|
||||
* functional style.
|
||||
*
|
||||
* @param <E> the type of event this handler handles
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EventHandler<E> {
|
||||
|
||||
/**
|
||||
* Called when the given event is fired from an event bus.
|
||||
*
|
||||
* @param event the event to handle
|
||||
*/
|
||||
void handle(E event);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Subscribes to a {@link TabEvent}.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Subscribe {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package me.neznamy.tab.api.event;
|
||||
|
||||
/**
|
||||
* An event that may be fired by TAB when something occurs.
|
||||
*
|
||||
* <p>This is a marker interface. All events <strong>must</strong> implement this
|
||||
* interface.
|
||||
*/
|
||||
public interface TabEvent {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package me.neznamy.tab.api.event.player;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Called when the player has been fully loaded by TAB.
|
||||
*/
|
||||
public interface PlayerLoadEvent extends TabEvent {
|
||||
|
||||
/**
|
||||
* Gets the player that was loaded.
|
||||
*
|
||||
* @return the player that was loaded
|
||||
*/
|
||||
@NotNull TabPlayer getPlayer();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player was loaded because they joined,
|
||||
* {@code false} if the player was loaded because tab reload command was executed
|
||||
*
|
||||
* @return {@code true} if player joined, {@code false} if plugin was reloaded
|
||||
*/
|
||||
boolean isJoin();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package me.neznamy.tab.api.event.plugin;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Called when an unknown placeholder is about to be registered.
|
||||
* It defaults to a PlaceholderAPI implementation, but can be overridden.
|
||||
*/
|
||||
public interface PlaceholderRegisterEvent extends TabEvent {
|
||||
|
||||
/**
|
||||
* Returns placeholder identifier starting and ending with {@code %}
|
||||
*
|
||||
* @return placeholder identifier starting and ending with {@code %}
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified server placeholder implementation.
|
||||
*
|
||||
* @param supplier
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setServerPlaceholder(@NonNull Supplier<Object> supplier);
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified player placeholder implementation.
|
||||
*
|
||||
* @param function
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setPlayerPlaceholder(@NonNull Function<TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified relational placeholder implementation.
|
||||
*
|
||||
* @param function
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setRelationalPlaceholder(@NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.neznamy.tab.api.event.plugin;
|
||||
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
|
||||
/**
|
||||
* Called when TAB is fully loaded.
|
||||
*/
|
||||
public interface TabLoadEvent extends TabEvent {
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package me.neznamy.tab.api.nametag;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for manipulating player name tags.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getNameTagManager()}.
|
||||
* This requires the Team feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
public interface NameTagManager {
|
||||
|
||||
/**
|
||||
* Makes player's NameTag globally invisible
|
||||
*
|
||||
* @param player
|
||||
* player to hide name tag of
|
||||
* @see #showNameTag(TabPlayer)
|
||||
* @see #hasHiddenNameTag(TabPlayer)
|
||||
*/
|
||||
void hideNameTag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Hides player's NameTag for specified player until it's shown again
|
||||
*
|
||||
* @param player
|
||||
* player to hide name tag of
|
||||
* @param viewer
|
||||
* player to hide name tag for
|
||||
*/
|
||||
void hideNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Makes player's NameTag visible again
|
||||
*
|
||||
* @param player
|
||||
* player to show name tag of
|
||||
* @see #hideNameTag(TabPlayer)
|
||||
* @see #hasHiddenNameTag(TabPlayer)
|
||||
*/
|
||||
void showNameTag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Shows player's NameTag for specified viewer if it was hidden before
|
||||
*
|
||||
* @param player
|
||||
* player to show name tag of
|
||||
* @param viewer
|
||||
* player to show NameTag back for
|
||||
*/
|
||||
void showNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Return whether player has hidden NameTag or not
|
||||
*
|
||||
* @param player
|
||||
* player to check name tag visibility status of
|
||||
* @return Whether player has hidden NameTag or not
|
||||
* @see #hideNameTag(TabPlayer)
|
||||
* @see #showNameTag(TabPlayer)
|
||||
*/
|
||||
boolean hasHiddenNameTag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns true if NameTag is hidden for specified viewer, false if not
|
||||
*
|
||||
* @param player
|
||||
* player to check visibility status of
|
||||
* @param viewer
|
||||
* player to check visibility status for
|
||||
* @return true if hidden, false if not
|
||||
*/
|
||||
boolean hasHiddenNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
|
||||
* This can be resumed using resumeTeamHandling(). If team handling was paused already, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* player to pause team handling of
|
||||
*/
|
||||
void pauseTeamHandling(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
|
||||
*
|
||||
* @param player
|
||||
* player to resume team handling of
|
||||
*/
|
||||
void resumeTeamHandling(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
|
||||
* it was resumed already using resumeTeamHandling
|
||||
*
|
||||
* @param player
|
||||
* player to check handling status of
|
||||
* @return true if paused, false if not
|
||||
*/
|
||||
boolean hasTeamHandlingPaused(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Forces collision rule for the player. Setting it to null will remove forced value
|
||||
*
|
||||
* @param player
|
||||
* player to set collision rule of
|
||||
* @param collision
|
||||
* forced collision rule
|
||||
*/
|
||||
void setCollisionRule(@NonNull TabPlayer player, @Nullable Boolean collision);
|
||||
|
||||
/**
|
||||
* Returns forced collision rule or null if collision is not forced using setCollisionRule
|
||||
*
|
||||
* @param player
|
||||
* player to get forced collision of
|
||||
* @return forced value or null if not forced
|
||||
*/
|
||||
@Nullable Boolean getCollisionRule(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes player's prefix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param prefix
|
||||
* new prefix value
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
|
||||
|
||||
/**
|
||||
* Changes player's suffix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param suffix
|
||||
* new suffix value
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
|
||||
|
||||
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
|
||||
|
||||
@NonNull String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Toggles name tag visibility view on all players for specified player.
|
||||
* On first call, name tags of all players will become invisible for specified
|
||||
* player. On second call, they will become visible again.
|
||||
*
|
||||
* @param player
|
||||
* player to toggle name tag visibility view for
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if configured toggle message should be sent, {@code false} if not
|
||||
*/
|
||||
void toggleNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has hidden name tags by either calling
|
||||
* {@link #toggleNameTagVisibilityView(TabPlayer, boolean)} or using a command,
|
||||
* {@code false} if not.
|
||||
*
|
||||
* @param player
|
||||
* player to check
|
||||
* @return {@code true} if hidden, {@code false} if not
|
||||
*/
|
||||
boolean hasHiddenNameTagVisibilityView(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package me.neznamy.tab.api.nametag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for manipulating player name tags.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getNameTagManager()} and then casting it.
|
||||
* This requires both Team and Unlimited name tag features to be enabled in config, otherwise the method will
|
||||
* return {@code null} or not be an instance of this class.
|
||||
*/
|
||||
public interface UnlimitedNameTagManager extends NameTagManager {
|
||||
|
||||
/**
|
||||
* Disables armor stands of specified player and displays vanilla name tags instead.
|
||||
*
|
||||
* @param player
|
||||
* Player to disable armor stands of
|
||||
* @see #enableArmorStands(TabPlayer)
|
||||
* @see #hasDisabledArmorStands(TabPlayer)
|
||||
*/
|
||||
void disableArmorStands(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Enabled back armor stands of player, who was disabled previously using
|
||||
* {@link #disableArmorStands(TabPlayer)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to enable back
|
||||
* @see #disableArmorStands(TabPlayer)
|
||||
* @see #hasDisabledArmorStands(TabPlayer)
|
||||
*/
|
||||
void enableArmorStands(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has armor stands disabled using
|
||||
* {@link #disableArmorStands(TabPlayer)}, {@code false} otherwise.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if disabled, {@code false} if not.
|
||||
* @see #disableArmorStands(TabPlayer)
|
||||
* @see #enableArmorStands(TabPlayer)
|
||||
*/
|
||||
boolean hasDisabledArmorStands(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes name to specified value. Set to {@code null} to
|
||||
* reset back to original value.
|
||||
*
|
||||
* @param player
|
||||
* Player to change name of
|
||||
* @param customName
|
||||
* New name, or {@code null} to reset to original value
|
||||
*/
|
||||
void setName(@NonNull TabPlayer player, @Nullable String customName);
|
||||
|
||||
/**
|
||||
* Changes line to specified value. Set to {@code null} to reset back to original value.
|
||||
*
|
||||
* @param player
|
||||
* Player to change line of
|
||||
* @param line
|
||||
* Name of defined line
|
||||
* @param value
|
||||
* Value to use, or {@code null} to reset back to original value
|
||||
* @see #getDefinedLines()
|
||||
*/
|
||||
void setLine(@NonNull TabPlayer player, @NonNull String line, @Nullable String value);
|
||||
|
||||
/**
|
||||
* Returns custom name value set using {@link #setName(TabPlayer, String)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return Custom name set previously or {@code null} if not set
|
||||
* @see #setName(TabPlayer, String)
|
||||
*/
|
||||
@Nullable String getCustomName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns custom line value set using {@link #setLine(TabPlayer, String, String)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @param line
|
||||
* Defined line
|
||||
* @return Custom name set previously or {@code null} if not set
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getDefinedLines()
|
||||
*/
|
||||
@Nullable String getCustomLineValue(@NonNull TabPlayer player, @NonNull String line);
|
||||
|
||||
/**
|
||||
* Returns original name value configured in files.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return Original configured name
|
||||
* @see #setName(TabPlayer, String)
|
||||
*/
|
||||
@NotNull String getOriginalName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original line value configured in files.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @param line
|
||||
* Line name
|
||||
* @return Original configured line
|
||||
* @see #setLine(TabPlayer, String, String)
|
||||
*/
|
||||
@NotNull String getOriginalLineValue(@NonNull TabPlayer player, @NonNull String line);
|
||||
|
||||
/**
|
||||
* Returns list of all defined lines in config, both static and dynamic lines.
|
||||
*
|
||||
* @return List of all defined lines in config
|
||||
*/
|
||||
@NotNull List<String> getDefinedLines();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An interface representing some kind of placeholder
|
||||
*/
|
||||
public interface Placeholder {
|
||||
|
||||
/**
|
||||
* Returns refresh interval of this placeholder
|
||||
*
|
||||
* @return refresh interval of this placeholder
|
||||
*/
|
||||
int getRefresh();
|
||||
|
||||
/**
|
||||
* Returns placeholder's identifier
|
||||
*
|
||||
* @return placeholder's identifier
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* An interface allowing placeholder registration. Instance can be
|
||||
* obtained using {@link TabAPI#getPlaceholderManager()}
|
||||
*/
|
||||
public interface PlaceholderManager {
|
||||
|
||||
/**
|
||||
* Registers a server placeholder (placeholder with same output for all players)
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Refresh interval
|
||||
* @param supplier
|
||||
* Supplier for placeholder output
|
||||
* @return Registered placeholder for further use
|
||||
* @throws IllegalArgumentException
|
||||
* If {@code identifier} does not start and end with {@code %} or
|
||||
* {@code refresh} is not divisible by 50
|
||||
*/
|
||||
@NotNull ServerPlaceholder registerServerPlaceholder(@NonNull String identifier, int refresh, @NonNull Supplier<Object> supplier);
|
||||
|
||||
/**
|
||||
* Registers a player placeholder (placeholder different output per player)
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Refresh interval
|
||||
* @param function
|
||||
* Function for placeholder output
|
||||
* @return Registered placeholder for further use
|
||||
* @throws IllegalArgumentException
|
||||
* If {@code identifier} does not start and end with {@code %} or
|
||||
* {@code refresh} is not divisible by 50
|
||||
*/
|
||||
@NotNull PlayerPlaceholder registerPlayerPlaceholder(@NonNull String identifier, int refresh, @NonNull Function<TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Registers a relational placeholder (placeholder with output different for each player duo)
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Refresh interval
|
||||
* @param function
|
||||
* Function for placeholder output
|
||||
* @return Registered placeholder for further use
|
||||
* @throws IllegalArgumentException
|
||||
* If {@code identifier} does not start and end with {@code %},
|
||||
* does not start with {@code %rel_} or
|
||||
* {@code refresh} is not divisible by 50
|
||||
*/
|
||||
@NotNull RelationalPlaceholder registerRelationalPlaceholder(@NonNull String identifier, int refresh, @NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
|
||||
|
||||
/**
|
||||
* Returns placeholder from specified identifier. If it does not exist, it is registered
|
||||
* as a PlaceholderAPI placeholder and returned.
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @return Placeholder with specified identifier
|
||||
*/
|
||||
@NotNull Placeholder getPlaceholder(@NonNull String identifier);
|
||||
|
||||
/**
|
||||
* Unregisters placeholder and makes plugin no longer refresh it.
|
||||
*
|
||||
* @param placeholder
|
||||
* Placeholder to unregister
|
||||
*/
|
||||
void unregisterPlaceholder(@NonNull Placeholder placeholder);
|
||||
|
||||
/**
|
||||
* Unregisters placeholder and makes plugin no longer refresh it.
|
||||
*
|
||||
* @param identifier
|
||||
* identifier of placeholder to unregister
|
||||
*/
|
||||
void unregisterPlaceholder(@NonNull String identifier);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for player placeholders (placeholders returning different value for each player)
|
||||
*/
|
||||
public interface PlayerPlaceholder extends Placeholder {
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
|
||||
* so this is (only) useful for force an update when value is supposed to change, and you have
|
||||
* a high refresh interval set.
|
||||
*
|
||||
* @param player
|
||||
* Player to update value for
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull TabPlayer player, @NonNull Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*
|
||||
* @param player
|
||||
* Player to update value for
|
||||
*/
|
||||
void update(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* An interface for relational placeholders (returning different value for each player duo)
|
||||
*/
|
||||
public interface RelationalPlaceholder extends Placeholder {
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
|
||||
* so this is (only) useful for force an update when value is supposed to change, and you have
|
||||
* a high refresh interval set.
|
||||
*
|
||||
* @param viewer
|
||||
* Placeholder viewer
|
||||
* @param target
|
||||
* Placeholder target
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @NonNull Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*
|
||||
* @param viewer
|
||||
* Placeholder viewer
|
||||
* @param target
|
||||
* Placeholder target
|
||||
*/
|
||||
void update(@NonNull TabPlayer viewer, @NonNull TabPlayer target);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* An interface for server placeholders (returning same value for all players)
|
||||
*/
|
||||
public interface ServerPlaceholder extends Placeholder {
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
|
||||
* so this is (only) useful for force an update when value is supposed to change, and you have
|
||||
* a high refresh interval set.
|
||||
*
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
*/
|
||||
void update();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An interface allowing to work with a line of
|
||||
* text in scoreboard.
|
||||
*
|
||||
*/
|
||||
public interface Line {
|
||||
|
||||
/**
|
||||
* Returns configured raw text of this line. Placeholders
|
||||
* remain in raw format.
|
||||
*
|
||||
* @return raw text of this line
|
||||
*/
|
||||
@NotNull String getText();
|
||||
|
||||
/**
|
||||
* Changes text to new value. Supports placeholders, which are
|
||||
* automatically registered if needed and refreshed periodically
|
||||
* based on configuration. No need to call this method to try to
|
||||
* keep placeholders up to date. If value is identical to previous
|
||||
* one, nothing happens.
|
||||
*
|
||||
* @param text
|
||||
* Raw text to display in the line
|
||||
*/
|
||||
void setText(@NonNull String text);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An interface allowing to work with scoreboard, such as adding players,
|
||||
* removing players and changing lines. New instance can be created using
|
||||
* {@link ScoreboardManager#createScoreboard(String, String, List)},
|
||||
* for scoreboards from config {@link ScoreboardManager#getRegisteredScoreboards()}.
|
||||
*/
|
||||
public interface Scoreboard {
|
||||
|
||||
/**
|
||||
* Returns internal name of this scoreboard. Name is defined in registration,
|
||||
* scoreboards from config use name they were defined with. This value is
|
||||
* used internally and in /tab announce scoreboard command.
|
||||
*
|
||||
* @return custom name of this scoreboard
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Returns raw title of this scoreboard. Placeholders stay in their
|
||||
* raw format.
|
||||
*
|
||||
* @return scoreboard title
|
||||
* @see #setTitle(String)
|
||||
*/
|
||||
@NotNull String getTitle();
|
||||
|
||||
/**
|
||||
* Sets title to specified value. Placeholders are refreshed automatically
|
||||
* with refresh intervals defined in config. No need to call this method
|
||||
* to try to keep placeholders up to date. Supports RGB codes using any of
|
||||
* the supported formats.
|
||||
* <p>
|
||||
* Length is limited to 32 characters on <1.13. If the limit is exceeded,
|
||||
* text will be cut to 32 characters.
|
||||
* <p>
|
||||
* Calling this method with same title as before will not do anything.
|
||||
*
|
||||
* @param title
|
||||
* New title to use with placeholder support
|
||||
* @see #getTitle()
|
||||
*/
|
||||
void setTitle(@NonNull String title);
|
||||
|
||||
/**
|
||||
* Returns list of lines in this scoreboard in the order they appear
|
||||
* in game (first line is on top). This list should only be used for reading,
|
||||
* for adding/removing lines see {@link #addLine(String)} and {@link #removeLine(int)}.
|
||||
*
|
||||
* @return list of lines in this scoreboard
|
||||
* @see #addLine(String)
|
||||
* @see #removeLine(int)
|
||||
*/
|
||||
@NotNull List<Line> getLines();
|
||||
|
||||
/**
|
||||
* Adds line with specified text on the bottom of scoreboard. Supports
|
||||
* placeholders, which will automatically be refreshed.
|
||||
*
|
||||
* @param text
|
||||
* Text to display
|
||||
* @see #getLines()
|
||||
* @see #removeLine(int)
|
||||
*/
|
||||
void addLine(@NonNull String text);
|
||||
|
||||
/**
|
||||
* Removes line with specified index. Index starts at {@code 0} and ends at
|
||||
* {@link #getLines()}.size()-1.
|
||||
*
|
||||
* @param index
|
||||
* Index of line to remove, starting at 0
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the index is out of range (index < 0 || index >= {@link #getLines()}.size())
|
||||
* @see #getLines()
|
||||
* @see #addLine(String)
|
||||
*/
|
||||
void removeLine(int index);
|
||||
|
||||
/**
|
||||
* Unregisters this scoreboard from all players who can see it.
|
||||
*/
|
||||
void unregister();
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Interface allowing work with scoreboards, such as creating,
|
||||
* sending, toggling and modifying.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link me.neznamy.tab.api.TabAPI#getScoreboardManager()}.
|
||||
* This requires the scoreboard feature to be enabled in config,
|
||||
* the method will return {@code null} otherwise.
|
||||
*/
|
||||
public interface ScoreboardManager {
|
||||
|
||||
/**
|
||||
* Creates new scoreboard, registers and returns it. {@code name}
|
||||
* is internal name of the scoreboard, which it will be registered
|
||||
* with and further work where scoreboard name is used allowed.
|
||||
* <p>
|
||||
* Scoreboard {@code title} is limited to 32 characters on <1.13. If needed,
|
||||
* it will be cut to 32 character for those players (after replacing
|
||||
* placeholders). RGB is supported using any of the supported formats.
|
||||
* Placeholders are supported.
|
||||
* <p>
|
||||
* {@code lines} will display in the same order as contained in the list.
|
||||
* Client can only see up to 15 lines at a time, however you can define
|
||||
* more, if some of them only consist of placeholder that might be empty.
|
||||
*
|
||||
* @param name
|
||||
* Internal name of the scoreboard
|
||||
* @param title
|
||||
* The scoreboard title
|
||||
* @param lines
|
||||
* Lines of text in scoreboard (supports placeholders)
|
||||
* @return The new scoreboard with given parameters
|
||||
* @throws NullPointerException
|
||||
* if {@code name} or {@code lines} is {@code null}
|
||||
*/
|
||||
@NotNull Scoreboard createScoreboard(@NonNull String name, @NonNull String title, @NonNull List<String> lines);
|
||||
|
||||
/**
|
||||
* Returns map of registered scoreboards via config and API.
|
||||
* Map key is name of scoreboard, which is defined either in config
|
||||
* or using {@link #createScoreboard(String, String, List)}.
|
||||
* Map value is reference to the actual scoreboard.
|
||||
*
|
||||
* @return map of registered scoreboards
|
||||
*/
|
||||
@NotNull Map<String, Scoreboard> getRegisteredScoreboards();
|
||||
|
||||
/**
|
||||
* Displays scoreboard to defined player. This will disable all display
|
||||
* condition checks until the scoreboard is unregistered using
|
||||
* {@link #resetScoreboard(TabPlayer)}. If the player already sees
|
||||
* this scoreboard, nothing will happen.
|
||||
*
|
||||
* @param player
|
||||
* Player to send scoreboard to
|
||||
* @param scoreboard
|
||||
* Scoreboard to display
|
||||
* @throws NullPointerException
|
||||
* if {@code player} or {@code scoreboard} is {@code null}
|
||||
* @see #hasCustomScoreboard(TabPlayer)
|
||||
* @see #resetScoreboard(TabPlayer)
|
||||
*/
|
||||
void showScoreboard(@NonNull TabPlayer player, @NonNull Scoreboard scoreboard);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has custom scoreboard set using
|
||||
* {@link #showScoreboard(TabPlayer, Scoreboard)}, {@code false} if not.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if player has custom scoreboard set using API,
|
||||
* {@code false} if not
|
||||
* @see #showScoreboard(TabPlayer, Scoreboard)
|
||||
* @see #resetScoreboard(TabPlayer)
|
||||
*/
|
||||
boolean hasCustomScoreboard(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Hides custom scoreboard sent using {@link #showScoreboard(TabPlayer, Scoreboard)}
|
||||
* and re-enables internal display logic with conditions. If player does not
|
||||
* have any forced scoreboard, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* Player to hide custom scoreboard from
|
||||
* @see #showScoreboard(TabPlayer, Scoreboard)
|
||||
* @see #hasCustomScoreboard(TabPlayer)
|
||||
*/
|
||||
void resetScoreboard(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has scoreboard enabled, {@code false}
|
||||
* if disabled (toggled) using either toggle command,
|
||||
* {@link #toggleScoreboard(TabPlayer, boolean)} or
|
||||
* {@link #setScoreboardVisible(TabPlayer, boolean, boolean)}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get visibility status of
|
||||
* @return {@code true} if visible, {@code false} if disabled
|
||||
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
|
||||
* @see #toggleScoreboard(TabPlayer, boolean)
|
||||
*/
|
||||
boolean hasScoreboardVisible(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sets scoreboard visibility of player to defined value. If visibility status
|
||||
* is same as player had before, nothing happens. If this call changes
|
||||
* visibility, scoreboard is toggled. If {@code sendToggleMessage} is {@code true},
|
||||
* toggle message defined in configuration is sent.
|
||||
*
|
||||
* @param player
|
||||
* Player to set visibility for
|
||||
* @param visible
|
||||
* New visibility status
|
||||
* @param sendToggleMessage
|
||||
* whether to send toggle message if status changed or not
|
||||
* @see #toggleScoreboard(TabPlayer, boolean)
|
||||
* @see #hasScoreboardVisible(TabPlayer)
|
||||
*/
|
||||
void setScoreboardVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Toggles scoreboard for specified player. If player had scoreboard visible,
|
||||
* hides it. If hidden, shows it. If {@code sendToggleMessage} is {@code true},
|
||||
* toggle message defined in configuration is sent.
|
||||
*
|
||||
* @param player
|
||||
* Player to toggle scoreboard for
|
||||
* @param sendToggleMessage
|
||||
* Whether to send toggle message or not
|
||||
* @see #hasScoreboardVisible(TabPlayer)
|
||||
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
|
||||
*/
|
||||
void toggleScoreboard(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Temporarily displays scoreboard to all players for specified amount of
|
||||
* time (in milliseconds). Scoreboard name is either defined in config, or
|
||||
* through API in {@link #createScoreboard(String, String, List)}.
|
||||
*
|
||||
* @param scoreboard
|
||||
* Scoreboard from config or registered via API
|
||||
* @param duration
|
||||
* Duration in milliseconds
|
||||
* @throws IllegalArgumentException
|
||||
* if no scoreboard was found with such name or {@code duration}
|
||||
* is < 0.
|
||||
*/
|
||||
void announceScoreboard(@NonNull String scoreboard, int duration);
|
||||
|
||||
/**
|
||||
* Returns player's currently displayed scoreboard. This can be either with
|
||||
* configuration, overridden with commands or the API. Will return {@code null}
|
||||
* if player does not see any scoreboard due to not meeting any display condition.
|
||||
*
|
||||
* @param player
|
||||
* player to get active scoreboard of
|
||||
* @return player's active scoreboard or {@code null} if player has no scoreboard
|
||||
*/
|
||||
@NotNull Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface allowing to send header/footer to players.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getHeaderFooterManager()}.
|
||||
* This requires the Header/footer feature to be enabled in config, otherwise
|
||||
* the method will return {@code null}.
|
||||
*/
|
||||
public interface HeaderFooterManager {
|
||||
|
||||
/**
|
||||
* Sets raw value of header to specified value. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value. Use {@code null} to reset value back to original.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param header
|
||||
* Raw header value
|
||||
*/
|
||||
void setHeader(@NonNull TabPlayer player, @Nullable String header);
|
||||
|
||||
/**
|
||||
* Sets raw value of footer to specified value. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value. Use {@code null} to reset value back to original.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param footer
|
||||
* Raw footer value
|
||||
*/
|
||||
void setFooter(@NonNull TabPlayer player, @Nullable String footer);
|
||||
|
||||
/**
|
||||
* Sets raw value of header and footer to specified values. This overrides plugin
|
||||
* configuration until reset is called. Has full placeholder support
|
||||
* and will send update packet whenever a placeholder used inside
|
||||
* changes its value. Use {@code null} to reset value back to original.
|
||||
*
|
||||
* @param player
|
||||
* Player to set header for
|
||||
* @param header
|
||||
* Raw header value
|
||||
* @param footer
|
||||
* Raw footer value
|
||||
*/
|
||||
void setHeaderAndFooter(@NonNull TabPlayer player, @Nullable String header, @Nullable String footer);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface SortingManager {
|
||||
|
||||
/**
|
||||
* Forces new team name for the player until this method is called again with null argument and
|
||||
* performs all actions to change player's team name
|
||||
*
|
||||
* @param player
|
||||
* player to set team name of
|
||||
* @param teamName
|
||||
* forced team name
|
||||
*/
|
||||
void forceTeamName(@NonNull TabPlayer player, @Nullable String teamName);
|
||||
|
||||
/**
|
||||
* Returns forced team name of player or null if not forced
|
||||
*
|
||||
* @param player
|
||||
* player to check forced team name of
|
||||
* @return forced team name of player or null if not forced
|
||||
*/
|
||||
@Nullable String getForcedTeamName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original team name of the player set by the plugin.
|
||||
*
|
||||
* @param player
|
||||
* Player to get team name of
|
||||
* @return Player's team name based on configuration.
|
||||
*/
|
||||
@NotNull String getOriginalTeamName(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface allowing to modify TabList display names
|
||||
* of players.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getTabListFormatManager()}.
|
||||
* This requires the feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
public interface TabListFormatManager {
|
||||
|
||||
/**
|
||||
* Changes player's prefix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param prefix
|
||||
* new prefix value
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
|
||||
|
||||
/**
|
||||
* Changes player's name to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param customName
|
||||
* new customName value
|
||||
* @see #getCustomName(TabPlayer)
|
||||
* @see #getOriginalName(TabPlayer)
|
||||
*/
|
||||
void setName(@NonNull TabPlayer player, @Nullable String customName);
|
||||
|
||||
/**
|
||||
* Changes player's suffix to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset
|
||||
* value back to original.
|
||||
*
|
||||
* @param player
|
||||
* player to change prefix of
|
||||
* @param suffix
|
||||
* new suffix value
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
/**
|
||||
* Returns prefix assigned using {@link #setPrefix(TabPlayer, String)} method.
|
||||
* If the method was not used, {@code null} is returned.
|
||||
*
|
||||
* @param player
|
||||
* player to get custom prefix of
|
||||
* @return player's custom prefix or {@code null} if not defined
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns custom name assigned using {@link #setName(TabPlayer, String)} method.
|
||||
* If the method was not used, {@code null} is returned.
|
||||
*
|
||||
* @param player
|
||||
* player to get custom name of
|
||||
* @return player's custom name or {@code null} if not defined
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getOriginalName(TabPlayer)
|
||||
*/
|
||||
@Nullable String getCustomName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns suffix assigned using {@link #setSuffix(TabPlayer, String)} method.
|
||||
* If the method was not used, {@code null} is returned.
|
||||
*
|
||||
* @param player
|
||||
* player to get custom suffix of
|
||||
* @return player's custom suffix or {@code null} if not defined
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original prefix applied using plugin's internal logic.
|
||||
* This value is not affected by API calls in any way.
|
||||
*
|
||||
* @param player
|
||||
* player to get original prefix of
|
||||
* @return player's original prefix
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
*/
|
||||
@NotNull String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original name applied using plugin's internal logic.
|
||||
* This value is not affected by API calls in any way.
|
||||
*
|
||||
* @param player
|
||||
* player to get original name of
|
||||
* @return player's original name
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getCustomName(TabPlayer)
|
||||
*/
|
||||
@NotNull String getOriginalName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original suffix applied using plugin's internal logic.
|
||||
* This value is not affected by API calls in any way.
|
||||
*
|
||||
* @param player
|
||||
* player to get original suffix of
|
||||
* @return player's original suffix
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
*/
|
||||
@NotNull String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package me.neznamy.tab.api.tablist.layout;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface representing a layout.
|
||||
*/
|
||||
public interface Layout {
|
||||
|
||||
/**
|
||||
* Returns name of the layout.
|
||||
*
|
||||
* @return name of the layout
|
||||
*/
|
||||
@NotNull String getName();
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text);
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
* @param skin
|
||||
* Skin definition like in config
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin);
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
* @param ping
|
||||
* Slot's ping
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, int ping);
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
* @param skin
|
||||
* Skin definition like in config
|
||||
* @param ping
|
||||
* Slot's ping
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin, int ping);
|
||||
|
||||
/**
|
||||
* Adds a player group.
|
||||
*
|
||||
* @param condition
|
||||
* Condition that must be met for player to appear in the group.
|
||||
* May be null for no requirement. Syntax is like in config -
|
||||
* either condition name or short format.
|
||||
* @param slots
|
||||
* Slots for this player group
|
||||
*/
|
||||
void addGroup(@Nullable String condition, int[] slots);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package me.neznamy.tab.api.tablist.layout;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for working with layouts. Instance can be obtained using
|
||||
* {@link TabAPI#getLayoutManager()}. If the feature is disabled in
|
||||
* config, it will return {@code null}.
|
||||
*/
|
||||
public interface LayoutManager {
|
||||
|
||||
/**
|
||||
* Creates new layout
|
||||
*
|
||||
* @param name
|
||||
* Unique layout name
|
||||
* @return Created layout
|
||||
*/
|
||||
Layout createNewLayout(String name);
|
||||
|
||||
/**
|
||||
* Sends layout to player. Set to {@code null} to make player not see
|
||||
* any layout, but default TabList instead.
|
||||
*
|
||||
* @param player
|
||||
* Player to send layout to
|
||||
* @param layout
|
||||
* Layout to send
|
||||
* @see #resetLayout(TabPlayer)
|
||||
*/
|
||||
void sendLayout(@NonNull TabPlayer player, @Nullable Layout layout);
|
||||
|
||||
/**
|
||||
* Reset layout back to original one based on configuration.
|
||||
*
|
||||
* @param player
|
||||
* Player to reset layout for
|
||||
* @see #sendLayout(TabPlayer, Layout)
|
||||
*/
|
||||
void resetLayout(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.johnrengelman:shadow:8.1.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.0.1")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.1-SNAPSHOT")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
rootProject.name = "build-logic"
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
maven("https://maven.fabricmc.net/")
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
id("io.freefair.lombok")
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
filter<ReplaceTokens>("tokens" to mapOf(
|
||||
"id" to rootProject.ext.get("id")!!.toString(),
|
||||
"name" to rootProject.name,
|
||||
"version" to project.version,
|
||||
"description" to project.description,
|
||||
"website" to rootProject.ext.get("website")!!.toString(),
|
||||
"author" to rootProject.ext.get("author")!!.toString()
|
||||
))
|
||||
}
|
||||
javadoc {
|
||||
// This saves a decent bit of processing power on slow machines
|
||||
enabled = false
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
|
||||
}
|
||||
compileJava {
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
options.release.set(8)
|
||||
options.compilerArgs.addAll(listOf("-nowarn", "-Xlint:-unchecked", "-Xlint:-deprecation"))
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(17))
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
@@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
id("tab.standard-conventions")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
}
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
archiveFileName.set("TAB-${project.name}-${project.version}.jar")
|
||||
relocate("org.bstats", "me.neznamy.tab.libs.org.bstats")
|
||||
relocate("org.json.simple", "me.neznamy.tab.libs.org.json.simple")
|
||||
relocate("net.kyori.event", "me.neznamy.tab.libs.net.kyori.event")
|
||||
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.me.neznamy.yamlassist")
|
||||
relocate("org.yaml.snakeyaml", "me.neznamy.tab.libs.org.yaml.snakeyaml")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id("tab.base-conventions")
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "krypton"
|
||||
url = uri("https://repo.kryptonmc.org/releases")
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
}
|
||||
publications.create<MavenPublication>("mavenJava") {
|
||||
groupId = rootProject.group as String
|
||||
artifactId = "tab-${project.name}"
|
||||
version = rootProject.version as String
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
plugins {
|
||||
id("tab.parent")
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "4.0.2"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
ext.set("website", "https://github.com/NEZNAMY/TAB")
|
||||
ext.set("author", "NEZNAMY")
|
||||
}
|
||||
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bungeecord,
|
||||
projects.krypton,
|
||||
projects.velocity,
|
||||
projects.sponge7,
|
||||
projects.sponge8,
|
||||
projects.fabric
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
projects.api,
|
||||
projects.shared
|
||||
).map { it.dependencyProject }
|
||||
|
||||
subprojects {
|
||||
when (this) {
|
||||
in platforms -> plugins.apply("tab.platform-conventions")
|
||||
in special -> plugins.apply("tab.standard-conventions")
|
||||
else -> plugins.apply("tab.base-conventions")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bukkit:3.0.1")
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
|
||||
compileOnly("me.clip:placeholderapi:2.11.2")
|
||||
compileOnly("com.mojang:authlib:1.5.25")
|
||||
compileOnly("com.github.MilkBowl:VaultAPI:1.7")
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
/**
|
||||
* The core for bukkit forwarding events into all enabled features
|
||||
*/
|
||||
public class BukkitEventListener extends EventListener<Player> implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e) {
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
join(e.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
worldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onCommand(PlayerCommandPreprocessEvent e) {
|
||||
if (command(e.getPlayer().getUniqueId(), e.getMessage())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabPlayer createPlayer(Player player) {
|
||||
return new BukkitTabPlayer(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Pipeline injection for bukkit
|
||||
*/
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BukkitPipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
try {
|
||||
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
|
||||
} catch (IllegalAccessException exception) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
|
||||
PacketScoreboard.DisplayObjective_POSITION.getInt(packet),
|
||||
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
PacketScoreboard.Objective_METHOD.getInt(packet),
|
||||
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayObjective(@NotNull Object packet) {
|
||||
return PacketScoreboard.DisplayObjectiveClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObjective(@NotNull Object packet) {
|
||||
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeam(@NotNull Object packet) {
|
||||
return PacketScoreboard.TeamPacketClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(@NotNull Object packet) {
|
||||
return BukkitTabList.PacketPlayOutPlayerInfoClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
List<String> actions;
|
||||
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
|
||||
//1.19.3+
|
||||
actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
|
||||
} else {
|
||||
//1.19.2-
|
||||
actions = Collections.singletonList(BukkitTabList.ACTION.get(packet).toString());
|
||||
}
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
|
||||
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_getProfile.invoke(nmsData);
|
||||
Object displayName = null;
|
||||
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
|
||||
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
|
||||
if (newDisplayName != null) displayName = nms.toNMSComponent(newDisplayName, receiver.getVersion());
|
||||
if (!nms.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
|
||||
}
|
||||
if (nms.is1_19_3Plus()) {
|
||||
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
|
||||
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
|
||||
profile.getId(),
|
||||
profile,
|
||||
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
|
||||
BukkitTabList.PlayerInfoData_Latency.getInt(nmsData),
|
||||
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
|
||||
displayName,
|
||||
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
|
||||
}
|
||||
}
|
||||
if (nms.is1_19_3Plus()) {
|
||||
BukkitTabList.PLAYERS.set(packet, updatedList);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
|
||||
if (TAB.getInstance().getNameTagManager() == null) return;
|
||||
int action = PacketScoreboard.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
|
||||
if (action == 1 || action == 2 || action == 4) return;
|
||||
Collection<String> players = (Collection<String>) PacketScoreboard.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
|
||||
String teamName = (String) PacketScoreboard.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
|
||||
if (players == null) return;
|
||||
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
|
||||
Collection<String> newList = new ArrayList<>();
|
||||
for (String entry : players) {
|
||||
TabPlayer p = TAB.getInstance().getPlayer(entry);
|
||||
if (p == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
String expectedTeam = sorting.getShortTeamName(p);
|
||||
if (expectedTeam == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
|
||||
logTeamOverride(teamName, p.getName(), expectedTeam);
|
||||
} else {
|
||||
newList.add(entry);
|
||||
}
|
||||
}
|
||||
PacketScoreboard.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Bukkit registry to register bukkit-only and universal placeholders
|
||||
*/
|
||||
public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
|
||||
private Chat chat;
|
||||
|
||||
/** NMS server to get TPS from on spigot */
|
||||
private Object server;
|
||||
|
||||
/** TPS field*/
|
||||
private Field recentTps;
|
||||
|
||||
/** Detection for presence of Paper's TPS getter */
|
||||
private Method paperTps;
|
||||
|
||||
/** Detection for presence of Paper's MSPT getter */
|
||||
private Method paperMspt;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads hooks
|
||||
*/
|
||||
public BukkitPlaceholderRegistry() {
|
||||
try {
|
||||
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
recentTps = server.getClass().getField("recentTps");
|
||||
} catch (ReflectiveOperationException e) {
|
||||
//not spigot
|
||||
}
|
||||
try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {}
|
||||
try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
|
||||
if (rspChat != null) chat = rspChat.getProvider();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
|
||||
if (paperTps != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> formatTPS(Bukkit.getTPS()[0]));
|
||||
} else if (recentTps != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> {
|
||||
try {
|
||||
return formatTPS(((double[]) recentTps.get(server))[0]);
|
||||
} catch (IllegalAccessException e) {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, -1, () -> -1);
|
||||
}
|
||||
if (paperMspt != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> format(Bukkit.getAverageTickTime()));
|
||||
}
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
|
||||
if (chat != null) {
|
||||
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
|
||||
} else {
|
||||
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
|
||||
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
|
||||
}
|
||||
super.registerPlaceholders(manager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.shared.GroupManager;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitNameTagX;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.hook.LuckPermsHook;
|
||||
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Implementation of Platform interface for Bukkit platform
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
/** Plugin instance for registering tasks and events */
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
/** Variables checking presence of other plugins to hook into */
|
||||
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
|
||||
@Setter private boolean libsDisguisesEnabled = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
|
||||
|
||||
public @NotNull BossBarManagerImpl getLegacyBossBar() {
|
||||
return new WitherBossBar(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (Player p : getOnlinePlayers()) {
|
||||
TAB.getInstance().addPlayer(new BukkitTabPlayer(p));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerPlaceholders() {
|
||||
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PipelineInjector createPipelineInjector() {
|
||||
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull NameTag getUnlimitedNameTags() {
|
||||
return new BukkitNameTagX(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TabExpansion createTabExpansion() {
|
||||
if (placeholderAPI) {
|
||||
BukkitTabExpansion expansion = new BukkitTabExpansion();
|
||||
expansion.register();
|
||||
return expansion;
|
||||
}
|
||||
return new EmptyTabExpansion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TabFeature getPerWorldPlayerList() {
|
||||
return new PerWorldPlayerList(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API
|
||||
*
|
||||
* @return online players from Bukkit API
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
private @NotNull Player[] getOnlinePlayers() {
|
||||
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
|
||||
if (players instanceof Player[]) {
|
||||
//1.7-
|
||||
return (Player[]) players;
|
||||
} else {
|
||||
//1.8+
|
||||
return ((Collection<Player>)players).toArray(new Player[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
|
||||
int refresh = pl.getRefreshInterval(identifier);
|
||||
if (identifier.startsWith("%rel_")) {
|
||||
//relational placeholder
|
||||
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRefreshInterval(identifier), (viewer, target) ->
|
||||
placeholderAPI ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
|
||||
} else if (identifier.startsWith("%sync:")) {
|
||||
String syncedPlaceholder = "%" + identifier.substring(6, identifier.length()-1) + "%";
|
||||
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
|
||||
ppl[0] = pl.registerPlayerPlaceholder(identifier, refresh, p -> {
|
||||
try {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
long time = System.nanoTime();
|
||||
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
|
||||
});
|
||||
return null;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
return "<Folia does not support sync placeholders>";
|
||||
}
|
||||
});
|
||||
} else if (identifier.startsWith("%server_")) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh, () ->
|
||||
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
|
||||
} else {
|
||||
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
|
||||
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
|
||||
TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerVersionInfo() {
|
||||
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0] + " (" + NMSStorage.getInstance().getServerPackage() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull public GroupManager detectPermissionPlugin() {
|
||||
if (LuckPermsHook.getInstance().isInstalled()) {
|
||||
return new GroupManager("LuckPerms", LuckPermsHook.getInstance().getGroupFunction());
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
|
||||
if (provider != null && !provider.getProvider().getName().equals("SuperPerms")) {
|
||||
return new GroupManager(provider.getProvider().getName(), p -> provider.getProvider().getPrimaryGroup((Player) p.getPlayer()));
|
||||
}
|
||||
}
|
||||
return new GroupManager("None", p -> TabConstants.NO_GROUP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.*;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Main class for Bukkit platform
|
||||
*/
|
||||
public class BukkitTAB extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
String version = Bukkit.getBukkitVersion().split("-")[0];
|
||||
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Server version: " + version + " (" + serverPackage + ")"));
|
||||
if (!isVersionSupported()) {
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
BukkitPlatform platform = new BukkitPlatform(this);
|
||||
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(version), getDataFolder()));
|
||||
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
|
||||
}
|
||||
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), this);
|
||||
TAB.getInstance().load();
|
||||
Metrics metrics = new Metrics(this, 5304);
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PLACEHOLDER_API, () -> platform.isPlaceholderAPI() ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION, () -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
|
||||
PluginCommand cmd = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
|
||||
if (cmd == null) return;
|
||||
BukkitTabCommand command = new BukkitTabCommand();
|
||||
cmd.setExecutor(command);
|
||||
cmd.setTabCompleter(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
//null check due to compatibility check making instance not get set on unsupported versions
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all used NMS classes, constructors, fields and methods.
|
||||
* Returns {@code true} if everything went successfully,
|
||||
* {@code false} if anything went wrong.
|
||||
*
|
||||
* @return {@code true} if server version is compatible, {@code false} if not
|
||||
*/
|
||||
private boolean isVersionSupported() {
|
||||
try {
|
||||
long time = System.currentTimeMillis();
|
||||
NMSStorage.setInstance(getNMSLoader());
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
|
||||
return true;
|
||||
} catch (IllegalStateException ex) {
|
||||
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is not compatible. " +
|
||||
"This plugin version was made for " + ProtocolVersion.values()[ProtocolVersion.values().length-1].getFriendlyName() +
|
||||
" - " + ProtocolVersion.values()[3].getFriendlyName() + ". Disabling."));
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] A compatibility issue " +
|
||||
"with your server was found. Unless you are running some really weird server software, this is a bug."));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull NMSStorage getNMSLoader() {
|
||||
List<Callable<NMSStorage>> loaders = Arrays.asList(
|
||||
BukkitLegacyNMSStorage::new,
|
||||
BukkitModernNMSStorage::new,
|
||||
MojangModernNMSStorage::new,
|
||||
ThermosNMSStorage::new
|
||||
);
|
||||
for (Callable<NMSStorage> loader : loaders) {
|
||||
try {
|
||||
return loader.call();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
throw new IllegalStateException("Unsupported server version");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
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.ArrayList;
|
||||
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, 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
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList which support modifying many entries at once
|
||||
* for significantly better performance. For 1.7 players,
|
||||
* ViaVersion properly splits the packet into multiple, so
|
||||
* we don't need to worry about that here.
|
||||
* <p>
|
||||
* This class does not support server versions of 1.7 and
|
||||
* below, because of the massive differences in tab list
|
||||
* and packet fields.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class BukkitTabList implements TabList {
|
||||
|
||||
// NMS Fields
|
||||
public static Class<?> PacketPlayOutPlayerListHeaderFooterClass;
|
||||
public static Constructor<?> newPacketPlayOutPlayerListHeaderFooter;
|
||||
public static Field HEADER;
|
||||
public static Field FOOTER;
|
||||
|
||||
public static Class<?> PacketPlayOutPlayerInfoClass;
|
||||
public static Constructor<?> newPacketPlayOutPlayerInfo;
|
||||
public static Field ACTION;
|
||||
public static Field PLAYERS;
|
||||
public static Class<Enum> EnumPlayerInfoActionClass;
|
||||
public static Class<Enum> EnumGamemodeClass;
|
||||
public static Class<?> ClientboundPlayerInfoRemovePacket;
|
||||
public static Class<?> RemoteChatSession$Data;
|
||||
public static Constructor<?> newClientboundPlayerInfoRemovePacket;
|
||||
|
||||
public static Class<?> PlayerInfoDataClass;
|
||||
public static Constructor<?> newPlayerInfoData;
|
||||
public static Method PlayerInfoData_getProfile;
|
||||
public static Field PlayerInfoData_Latency;
|
||||
public static Field PlayerInfoData_GameMode;
|
||||
public static Field PlayerInfoData_DisplayName;
|
||||
public static Field PlayerInfoData_Listed;
|
||||
public static Field PlayerInfoData_RemoteChatSession;
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
public static void load(NMSStorage nms) throws NoSuchMethodException {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor(nms.IChatBaseComponent, nms.IChatBaseComponent);
|
||||
} else {
|
||||
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor();
|
||||
HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(0);
|
||||
FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(1);
|
||||
}
|
||||
if (nms.is1_19_3Plus()) {
|
||||
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
|
||||
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
|
||||
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumSet.class);
|
||||
} else {
|
||||
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumPlayerInfoActionClass, Array.newInstance(nms.EntityPlayer, 0).getClass());
|
||||
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumPlayerInfoActionClass);
|
||||
}
|
||||
PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, List.class);
|
||||
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoDataClass);
|
||||
PlayerInfoData_getProfile = ReflectionUtils.getOnlyMethod(PlayerInfoDataClass, GameProfile.class);
|
||||
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(PlayerInfoDataClass, int.class);
|
||||
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoDataClass, EnumGamemodeClass);
|
||||
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoDataClass, nms.IChatBaseComponent);
|
||||
if (nms.is1_19_3Plus()) {
|
||||
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(PlayerInfoDataClass, boolean.class);
|
||||
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(PlayerInfoDataClass, RemoteChatSession$Data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
if (ClientboundPlayerInfoRemovePacket != null) {
|
||||
//1.19.3+
|
||||
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
|
||||
} else {
|
||||
//1.19.2-
|
||||
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Entry.Builder(entry).build(), player.getVersion()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME,
|
||||
new Entry.Builder(entry).displayName(displayName).build(), player.getVersion())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
player.sendPacket(createPacket(Action.UPDATE_LATENCY,
|
||||
new Entry.Builder(entry).latency(latency).build(), player.getVersion())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE,
|
||||
new Entry.Builder(entry).gameMode(gameMode).build(), player.getVersion())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
player.sendPacket(createPacket(Action.ADD_PLAYER, entry, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
if (PacketPlayOutPlayerListHeaderFooterClass == null) return;
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object packet;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance(
|
||||
nms.toNMSComponent(header, player.getVersion()), nms.toNMSComponent(footer, player.getVersion()));
|
||||
} else {
|
||||
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance();
|
||||
HEADER.set(packet, nms.toNMSComponent(header, player.getVersion()));
|
||||
FOOTER.set(packet, nms.toNMSComponent(footer, player.getVersion()));
|
||||
}
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createPacket(TabList.Action action, TabList.Entry entry, ProtocolVersion clientVersion) {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
Object packet;
|
||||
List<Object> players = new ArrayList<>();
|
||||
if (NMSStorage.getInstance().is1_19_3Plus()) {
|
||||
EnumSet<?> actions;
|
||||
if (action == TabList.Action.ADD_PLAYER) {
|
||||
actions = EnumSet.allOf(EnumPlayerInfoActionClass);
|
||||
} else {
|
||||
actions = EnumSet.of(Enum.valueOf(EnumPlayerInfoActionClass, action.name()));
|
||||
}
|
||||
packet = newPacketPlayOutPlayerInfo.newInstance(actions, Collections.emptyList());
|
||||
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
|
||||
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
|
||||
players.add(newPlayerInfoData.newInstance(
|
||||
entry.getUniqueId(),
|
||||
profile,
|
||||
true,
|
||||
entry.getLatency(),
|
||||
int2GameMode(entry.getGameMode()),
|
||||
entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion),
|
||||
null
|
||||
));
|
||||
} else {
|
||||
packet = newPacketPlayOutPlayerInfo.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action.name()),
|
||||
Array.newInstance(NMSStorage.getInstance().EntityPlayer, 0));
|
||||
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
|
||||
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (newPlayerInfoData.getParameterTypes()[0] == PacketPlayOutPlayerInfoClass) {
|
||||
parameters.add(packet);
|
||||
}
|
||||
parameters.add(profile);
|
||||
parameters.add(entry.getLatency());
|
||||
parameters.add(int2GameMode(entry.getGameMode()));
|
||||
parameters.add(entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion));
|
||||
if (nms.getMinorVersion() >= 19) parameters.add(null);
|
||||
players.add(newPlayerInfoData.newInstance(parameters.toArray()));
|
||||
}
|
||||
PLAYERS.set(packet, players);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private Object int2GameMode(int gameMode) {
|
||||
switch (gameMode) {
|
||||
case 1: return Enum.valueOf(EnumGamemodeClass, "CREATIVE");
|
||||
case 2: return Enum.valueOf(EnumGamemodeClass, "ADVENTURE");
|
||||
case 3: return Enum.valueOf(EnumGamemodeClass, "SPECTATOR");
|
||||
default: return Enum.valueOf(EnumGamemodeClass, "SURVIVAL");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.shared.hook.ViaVersionHook;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.EntityBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for Bukkit platform
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Getter
|
||||
public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
/** Player's NMS handle (EntityPlayer), preloading for speed */
|
||||
private final Object handle;
|
||||
|
||||
/** Player's connection for sending packets, preloading for speed */
|
||||
private final Object playerConnection;
|
||||
|
||||
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
|
||||
private final TabList tabList = new BukkitTabList(this);
|
||||
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
|
||||
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given bukkit player and protocol version
|
||||
*
|
||||
* @param p
|
||||
* bukkit player
|
||||
*/
|
||||
@SneakyThrows
|
||||
public BukkitTabPlayer(Player p) {
|
||||
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(),
|
||||
p.getWorld().getName(), ViaVersionHook.getInstance().getPlayerVersion(p.getUniqueId(), p.getName()));
|
||||
handle = NMSStorage.getInstance().getHandle.invoke(player);
|
||||
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String permission) {
|
||||
return getPlayer().hasPermission(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
|
||||
return getPlayer().getPing();
|
||||
}
|
||||
try {
|
||||
return NMSStorage.getInstance().PING.getInt(handle);
|
||||
} catch (IllegalAccessException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void sendPacket(@Nullable Object nmsPacket) {
|
||||
if (nmsPacket == null || !getPlayer().isOnline()) return;
|
||||
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
getPlayer().sendMessage(RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
|
||||
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return getPlayer().hasPotionEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public TabList.Skin getSkin() {
|
||||
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
return new TabList.Skin(property.getValue(), property.getSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Player getPlayer() {
|
||||
return (Player) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer().isOnline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().getGameMode().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data) {
|
||||
sendPacket(PacketPlayOutSpawnEntityLivingStorage.build(entityId, id, entityType, location, data));
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
|
||||
updateEntityMetadata(entityId, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
if (PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.getParameterCount() == 2) {
|
||||
//1.19.3+
|
||||
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(data.build())));
|
||||
} else {
|
||||
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, data.build(), true));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleportEntity(int entityId, @NotNull Location location) {
|
||||
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void destroyEntities(int... entities) {
|
||||
if (PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.getParameterTypes()[0] != int.class) {
|
||||
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(new Object[]{entities}));
|
||||
} else {
|
||||
//1.17.0 Mojank
|
||||
for (int entity : entities) {
|
||||
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
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 {
|
||||
|
||||
/** Player this handler belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
/** Bukkit BossBars the player can currently see */
|
||||
private final Map<UUID, org.bukkit.boss.BossBar> bossBars = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
if (bossBars.containsKey(id)) return;
|
||||
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
|
||||
RGBUtils.getInstance().convertToBukkitFormat(title,
|
||||
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
|
||||
org.bukkit.boss.BarColor.valueOf(color.name()),
|
||||
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
|
||||
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(RGBUtils.getInstance().convertToBukkitFormat(title,
|
||||
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
bossBars.get(id).setProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
|
||||
}
|
||||
|
||||
@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,64 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* BossBar using wither entity for <1.9 players on <1.9 servers.
|
||||
* Additional logic, such as teleporting the entity must be done
|
||||
* separately, as this class does not handle it.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class EntityBossBar implements BossBar {
|
||||
|
||||
/** Player this handler belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
float health = 300*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.getHelper().setHealth(health);
|
||||
w.getHelper().setCustomName(title, player.getVersion());
|
||||
w.getHelper().setEntityFlags((byte) 32);
|
||||
w.getHelper().setWitherInvulnerableTime(880); // Magic number
|
||||
player.spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0, 0, 0), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
w.getHelper().setCustomName(title, player.getVersion());
|
||||
player.updateEntityMetadata(id.hashCode(), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
float health = 300*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.getHelper().setHealth(health);
|
||||
player.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.destroyEntities(id.hashCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
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.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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 {
|
||||
|
||||
/** Player this handler belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
/** 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<>();
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
if (viaBossBars.containsKey(id)) return;
|
||||
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
|
||||
RGBUtils.getInstance().convertToBukkitFormat(title, player.getVersion().getMinorVersion() >= 16),
|
||||
progress,
|
||||
BossColor.valueOf(color.name()),
|
||||
BossStyle.valueOf(style.getBukkitName()));
|
||||
viaBossBars.put(id, bar);
|
||||
bar.addPlayer(player.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
viaBossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
|
||||
player.getVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
|
||||
@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(BossStyle.valueOf(style.getBukkitName()));
|
||||
}
|
||||
|
||||
@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,193 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.features.types.PacketSendListener;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Pose;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The core class for unlimited NameTag mode on Bukkit
|
||||
*/
|
||||
public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketSendListener {
|
||||
|
||||
/** Reference to NMS storage for quick access */
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
|
||||
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onSneak(PlayerToggleSneakEvent e) {
|
||||
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (p == null || isPlayerDisabled(p)) return;
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_SNEAK,
|
||||
() -> getArmorStandManager(p).sneak(e.isSneaking()));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (respawned == null || isPlayerDisabled(respawned)) return;
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
|
||||
() -> getArmorStandManager(respawned).teleport());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
if (receiver.getVersion().getMinorVersion() < 8) return;
|
||||
if (!receiver.isLoaded() || getDisableChecker().isDisabledPlayer(receiver) || getUnlimitedDisableChecker().isDisabledPlayer(receiver)) return;
|
||||
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
|
||||
packetListener.onEntityMove((BackendTabPlayer) receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
|
||||
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
|
||||
packetListener.onEntityMove((BackendTabPlayer) receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
|
||||
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
|
||||
packetListener.onEntitySpawn((BackendTabPlayer) receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
|
||||
} else if (PacketPlayOutEntityDestroyStorage.CLASS.isInstance(packet)) {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
Object entities = PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet);
|
||||
if (entities instanceof List) {
|
||||
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (List<Integer>) entities);
|
||||
} else {
|
||||
//1.17.0
|
||||
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int) entities);
|
||||
}
|
||||
} else {
|
||||
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
public @NotNull List<Integer> getPassengers(@NotNull Object entity) {
|
||||
Entity vehicle = (Entity) entity;
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
|
||||
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
|
||||
} else {
|
||||
if (vehicle.getPassenger() != null) {
|
||||
return Collections.singletonList(vehicle.getPassenger().getEntityId());
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getVehicle(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getVehicle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEntityId(@NotNull Object entity) {
|
||||
return ((Entity)entity).getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getEntityType(@NotNull Object entity) {
|
||||
return ((Entity) entity).getType().toString().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).isSneaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSwimming(@NotNull TabPlayer player) {
|
||||
Player p = (Player) player.getPlayer();
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
|
||||
return TAB.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGliding(@NotNull TabPlayer player) {
|
||||
return TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSleeping(@NotNull TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).isSleeping();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull 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
|
||||
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
|
||||
DataWatcher datawatcher = new DataWatcher();
|
||||
datawatcher.getHelper().setEntityFlags(flags);
|
||||
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
|
||||
datawatcher.getHelper().setCustomNameVisible(nameVisible);
|
||||
datawatcher.getHelper().setArmorStandFlags((byte)16);
|
||||
return datawatcher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import 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;
|
||||
|
||||
/**
|
||||
* 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<>();
|
||||
|
||||
private final String author = TabConstants.PLUGIN_AUTHOR;
|
||||
private final String identifier = TabConstants.PLUGIN_ID;
|
||||
private final String version = TabConstants.PLUGIN_VERSION;
|
||||
|
||||
@Override
|
||||
public boolean persist() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onPlaceholderRequest(@Nullable Player player, @NotNull String identifier) {
|
||||
if (identifier.startsWith("replace_")) {
|
||||
String text = "%" + identifier.substring(8) + "%";
|
||||
String textBefore;
|
||||
do {
|
||||
textBefore = text;
|
||||
for (String placeholder : TAB.getInstance().getPlaceholderManager().detectPlaceholders(text)) {
|
||||
text = text.replace(placeholder, TAB.getInstance().getPlaceholderManager().findReplacement(placeholder,
|
||||
PlaceholderAPI.setPlaceholders(player, placeholder)));
|
||||
}
|
||||
} while (!textBefore.equals(text));
|
||||
return text;
|
||||
}
|
||||
if (identifier.startsWith("placeholder_")) {
|
||||
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TAB.getInstance().getPlaceholderManager());
|
||||
}
|
||||
return values.get(player).get(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
|
||||
values.computeIfAbsent((Player) player.getPlayer(), p -> new HashMap<>()).put(key, value);
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
import me.neznamy.tab.shared.features.types.UnLoadable;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Per-world-PlayerList feature handler
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@RequiredArgsConstructor
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
|
||||
|
||||
/** Config options */
|
||||
private final boolean allowBypass = TAB.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
private final List<String> ignoredWorlds = TAB.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
|
||||
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
|
||||
/** Plugin reference*/
|
||||
@NotNull private final JavaPlugin plugin;
|
||||
|
||||
@Getter private final String featureName = "Per world PlayerList";
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
}
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs visibility check on the player. Shows players this player should see and does not,
|
||||
* hides players the player should not see, shows the player to those who should see player and hides
|
||||
* from those, who should not see.
|
||||
*
|
||||
* @param p
|
||||
* Player to update
|
||||
*/
|
||||
private void checkPlayer(@NotNull Player p) {
|
||||
for (Player all : Bukkit.getOnlinePlayers()) {
|
||||
if (all == p) continue;
|
||||
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
|
||||
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
|
||||
if (!shouldSee(all, p) && all.canSee(p)) all.hidePlayer(p);
|
||||
if (shouldSee(all, p) && !all.canSee(p)) all.showPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if viewer should see target player, {@code false} if not.
|
||||
* @param viewer
|
||||
* Player viewing the TabList
|
||||
* @param target
|
||||
* Target displayed in the TabList
|
||||
* @return {@code true} if viewer should see target, {@code false} if not.
|
||||
*/
|
||||
private boolean shouldSee(@NotNull Player viewer, @NotNull Player target) {
|
||||
if (target == viewer) return true;
|
||||
if ((allowBypass && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || ignoredWorlds.contains(viewer.getWorld().getName())) return true;
|
||||
String viewerWorldGroup = viewer.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
|
||||
String targetWorldGroup = target.getWorld().getName() + "-default";
|
||||
for (Entry<String, List<String>> group : sharedWorlds.entrySet()) {
|
||||
if (group.getValue() != null) {
|
||||
if (group.getValue().contains(viewer.getWorld().getName())) viewerWorldGroup = group.getKey();
|
||||
if (group.getValue().contains(target.getWorld().getName())) targetWorldGroup = group.getKey();
|
||||
}
|
||||
}
|
||||
return viewerWorldGroup.equals(targetWorldGroup);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.api.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.features.types.WorldSwitchListener;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An additional class with additional code for <1.9 servers due to an entity being required
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
|
||||
|
||||
/** Distance of the Wither in blocks */
|
||||
private static final int WITHER_DISTANCE = 60;
|
||||
|
||||
/** Reference to plugin for registering listener */
|
||||
private final @NotNull JavaPlugin plugin;
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
|
||||
//when in a small window, it's about 100ms
|
||||
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
|
||||
featureName, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
super.load();
|
||||
teleport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Wither location for all online players
|
||||
*/
|
||||
private void teleport() {
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
|
||||
for (BossBar line : getRegisteredBossBars().values()) {
|
||||
if (!line.getPlayers().contains(p)) continue;
|
||||
Location loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
|
||||
if (loc.getY() < 1) loc.setY(1);
|
||||
((BackendTabPlayer)p).teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ(), 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
super.unload();
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning wither as respawn screen destroys all entities in client
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TabPlayer player = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player == null) return;
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
|
||||
() -> detectBossBarsAndSend(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWorldChange(@NotNull TabPlayer p, @NotNull String from, @NotNull String to) {
|
||||
for (BossBar line : lineValues) {
|
||||
line.removePlayer(p);
|
||||
}
|
||||
detectBossBarsAndSend(p);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.ToString;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcher class to make work with it much easier
|
||||
*/
|
||||
@ToString
|
||||
public class DataWatcher implements EntityData {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
public static Method REGISTER;
|
||||
public static Method markDirty;
|
||||
public static Method packDirty;
|
||||
|
||||
/** Watched data */
|
||||
private final Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
|
||||
|
||||
/** Helper for easier data write */
|
||||
@Getter private final DataWatcherHelper helper = new DataWatcherHelper(this);
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @param nms
|
||||
* NMS storage reference
|
||||
*/
|
||||
public static void load(NMSStorage nms) {
|
||||
CONSTRUCTOR = ReflectionUtils.getOnlyConstructor(CLASS);
|
||||
if (nms.is1_19_3Plus()) {
|
||||
markDirty = ReflectionUtils.getOnlyMethod(CLASS, void.class, DataWatcherObject.CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value into data values
|
||||
*
|
||||
* @param type
|
||||
* type of value
|
||||
* @param value
|
||||
* value
|
||||
*/
|
||||
public void setValue(@NotNull DataWatcherObject type, @NotNull Object value) {
|
||||
dataValues.put(type.getPosition(), new DataWatcherItem(type, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the class into an instance of NMS.DataWatcher
|
||||
*
|
||||
* @return an instance of NMS.DataWatcher with same data
|
||||
*/
|
||||
@SneakyThrows
|
||||
public @NotNull Object build() {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object nmsWatcher;
|
||||
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
|
||||
nmsWatcher = CONSTRUCTOR.newInstance(new Object[] {null});
|
||||
} else {
|
||||
nmsWatcher = CONSTRUCTOR.newInstance();
|
||||
}
|
||||
for (DataWatcherItem item : dataValues.values()) {
|
||||
Object nmsObject = item.getType().build();
|
||||
REGISTER.invoke(nmsWatcher, nmsObject, item.getValue());
|
||||
if (nms.is1_19_3Plus()) markDirty.invoke(nmsWatcher, nmsObject);
|
||||
}
|
||||
return nmsWatcher;
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A class to help to assign DataWatcher items as positions often change per-version
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class DataWatcherHelper {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> DataWatcherRegistry;
|
||||
public static Class<?> DataWatcherSerializer;
|
||||
public static Object DataWatcherSerializer_BYTE;
|
||||
public static Object DataWatcherSerializer_FLOAT;
|
||||
public static Object DataWatcherSerializer_STRING;
|
||||
public static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
|
||||
public static Object DataWatcherSerializer_BOOLEAN;
|
||||
|
||||
/** Instance fields */
|
||||
private final int armorStandFlagsPosition = getArmorStandFlagsPosition();
|
||||
@NotNull private final DataWatcher data;
|
||||
|
||||
/**
|
||||
* Returns armor stand flags position based on server version
|
||||
*
|
||||
* @return armor stand flags position based on server version
|
||||
*/
|
||||
private int getArmorStandFlagsPosition() {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
|
||||
//1.17.x, 1.18.x, 1.19.x
|
||||
return 15;
|
||||
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
|
||||
//1.15.x, 1.16.x
|
||||
return 14;
|
||||
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14) {
|
||||
//1.14.x
|
||||
return 13;
|
||||
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 10) {
|
||||
//1.10.x - 1.13.x
|
||||
return 11;
|
||||
} else {
|
||||
//1.8.x - 1.9.x
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity byte flags
|
||||
*
|
||||
* @param flags
|
||||
* flags to write
|
||||
*/
|
||||
public void setEntityFlags(byte flags) {
|
||||
data.setValue(new DataWatcherObject(0, DataWatcherSerializer_BYTE), flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
|
||||
*
|
||||
* @param customName
|
||||
* target custom name
|
||||
* @param clientVersion
|
||||
* client version
|
||||
*/
|
||||
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 13) {
|
||||
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_OPTIONAL_COMPONENT),
|
||||
Optional.ofNullable(NMSStorage.getInstance().toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
|
||||
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8) {
|
||||
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_STRING), customName);
|
||||
} else {
|
||||
//name length is limited to 64 characters on <1.8
|
||||
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
|
||||
data.setValue(new DataWatcherObject(10, null), cutName);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(5, null), cutName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes custom name visibility boolean
|
||||
*
|
||||
* @param visible
|
||||
* if visible or not
|
||||
*/
|
||||
public void setCustomNameVisible(boolean visible) {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
|
||||
data.setValue(new DataWatcherObject(3, DataWatcherSerializer_BOOLEAN), visible);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(3, null), (byte)(visible?1:0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity health
|
||||
*
|
||||
* @param health
|
||||
* health of entity
|
||||
*/
|
||||
public void setHealth(float health) {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
|
||||
data.setValue(new DataWatcherObject(6, DataWatcherSerializer_FLOAT), health);
|
||||
} else {
|
||||
data.setValue(new DataWatcherObject(16, null), (int)health);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes armor stand flags
|
||||
*
|
||||
* @param flags
|
||||
* flags to write
|
||||
*/
|
||||
public void setArmorStandFlags(byte flags) {
|
||||
data.setValue(new DataWatcherObject(armorStandFlagsPosition, DataWatcherSerializer_BYTE), flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes wither invulnerable time
|
||||
* @param time
|
||||
* Time, apparently
|
||||
*/
|
||||
public void setWitherInvulnerableTime(int time) {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() > 8)
|
||||
throw new UnsupportedOperationException("Not supported on 1.9+");
|
||||
data.setValue(new DataWatcherObject(20, null), time);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import lombok.Data;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Class representing NMS Data Watcher Item
|
||||
*/
|
||||
@Data
|
||||
public class DataWatcherItem {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Field TYPE;
|
||||
public static Field VALUE;
|
||||
|
||||
/** Instance fields */
|
||||
@NotNull private final DataWatcherObject type;
|
||||
@NotNull private final Object value;
|
||||
|
||||
/**
|
||||
* Loads all required Fields
|
||||
*
|
||||
* @param nms
|
||||
* NMS storage reference
|
||||
*/
|
||||
public static void load(NMSStorage nms) {
|
||||
VALUE = ReflectionUtils.getFields(CLASS, Object.class).get(0);
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
TYPE = ReflectionUtils.getOnlyField(CLASS, DataWatcherObject.CLASS);
|
||||
} else {
|
||||
TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcherObject class to make work with it much easier
|
||||
*/
|
||||
@Data @AllArgsConstructor
|
||||
public class DataWatcherObject {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
public static Field SLOT;
|
||||
public static Field SERIALIZER;
|
||||
|
||||
/** Instance fields */
|
||||
private final int position;
|
||||
@Nullable private final Object serializer;
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @throws NoSuchMethodException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load() throws NoSuchMethodException {
|
||||
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcherHelper.DataWatcherSerializer);
|
||||
SLOT = ReflectionUtils.getOnlyField(CLASS, int.class);
|
||||
SERIALIZER = ReflectionUtils.getOnlyField(CLASS, DataWatcherHelper.DataWatcherSerializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the object into NMS object
|
||||
*
|
||||
* @return NMS object
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object build() {
|
||||
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
|
||||
return DataWatcherObject.CONSTRUCTOR.newInstance(position, serializer);
|
||||
} else {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* NMS loader for Minecraft 1.5.2 - 1.16.5 using Bukkit mapping.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class BukkitLegacyNMSStorage extends NMSStorage {
|
||||
|
||||
/**
|
||||
* Returns class with given potential names in same order
|
||||
*
|
||||
* @param names
|
||||
* possible class names
|
||||
* @return class for specified names
|
||||
* @throws ClassNotFoundException
|
||||
* if class does not exist
|
||||
*/
|
||||
private Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
|
||||
for (String name : names) {
|
||||
try {
|
||||
return getLegacyClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
//not the first class name in array
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class from given name
|
||||
*
|
||||
* @param name
|
||||
* class name
|
||||
* @return class from given name
|
||||
* @throws ClassNotFoundException
|
||||
* if class was not found
|
||||
*/
|
||||
public Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
|
||||
return Class.forName("net.minecraft.server." + serverPackage + "." + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
|
||||
PacketScoreboard.ScoreboardScore_setScore = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardScoreClass, new String[] {"setScore", "c"}, int.class); // 1.5.1+, 1.5
|
||||
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setAllowFriendlyFire", "a"}, boolean.class); // 1.5.1+, 1.5
|
||||
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setCanSeeFriendlyInvisibles", "b"}, boolean.class); // 1.5.1+, 1.5
|
||||
if (minorVersion >= 7) {
|
||||
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setNameTagVisibility", "a"}, PacketScoreboard.EnumNameTagVisibility); // {1.8.1+, 1.8}
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("register", DataWatcherObject.CLASS, Object.class);
|
||||
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("a").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("c").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
|
||||
if (getMinorVersion() >= 13) {
|
||||
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
|
||||
} else {
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("h").get(null);
|
||||
}
|
||||
} else {
|
||||
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("a", int.class, Object.class);
|
||||
}
|
||||
if (minorVersion >= 13) {
|
||||
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("setPrefix", IChatBaseComponent);
|
||||
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("setSuffix", IChatBaseComponent);
|
||||
} else {
|
||||
PacketScoreboard.ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setPrefix", "b"}, String.class); // 1.5.1+, 1.5
|
||||
PacketScoreboard.ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setSuffix", "c"}, String.class); // 1.5.1+, 1.5
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadClasses() throws ClassNotFoundException {
|
||||
EntityHuman = getLegacyClass("EntityHuman");
|
||||
World = getLegacyClass("World");
|
||||
Packet = getLegacyClass("Packet");
|
||||
EnumChatFormat = (Class<Enum>) getLegacyClass("EnumChatFormat");
|
||||
EntityPlayer = getLegacyClass("EntityPlayer");
|
||||
Entity = getLegacyClass("Entity");
|
||||
EntityLiving = getLegacyClass("EntityLiving");
|
||||
PlayerConnection = getLegacyClass("PlayerConnection");
|
||||
NetworkManager = getLegacyClass("NetworkManager");
|
||||
DataWatcher.CLASS = getLegacyClass("DataWatcher");
|
||||
DataWatcherItem.CLASS = getLegacyClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
|
||||
PacketPlayOutSpawnEntityLivingStorage.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
|
||||
PacketPlayOutEntityTeleportStorage.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
|
||||
PacketPlayOutEntity = getLegacyClass("PacketPlayOutEntity", "Packet30Entity");
|
||||
PacketPlayOutEntityDestroyStorage.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
|
||||
PacketPlayOutEntityLook = getLegacyClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
|
||||
PacketPlayOutEntityMetadataStorage.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
|
||||
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
|
||||
PacketScoreboard.DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
|
||||
PacketScoreboard.ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
|
||||
PacketScoreboard.TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
|
||||
PacketScoreboard.ScorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
|
||||
PacketScoreboard.Scoreboard = getLegacyClass("Scoreboard");
|
||||
PacketScoreboard.ScoreboardObjective = getLegacyClass("ScoreboardObjective");
|
||||
PacketScoreboard.ScoreboardScoreClass = getLegacyClass("ScoreboardScore");
|
||||
PacketScoreboard.IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
|
||||
PacketScoreboard.ScoreboardTeam = getLegacyClass("ScoreboardTeam");
|
||||
if (minorVersion >= 7) {
|
||||
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
|
||||
ChatSerializer = getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
|
||||
EntityArmorStand = getLegacyClass("EntityArmorStand");
|
||||
BukkitTabList.PacketPlayOutPlayerInfoClass = getLegacyClass("PacketPlayOutPlayerInfo");
|
||||
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
|
||||
BukkitTabList.PlayerInfoDataClass = getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
|
||||
BukkitTabList.EnumGamemodeClass = (Class<Enum>) getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
|
||||
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
|
||||
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
|
||||
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcherObject.CLASS = getLegacyClass("DataWatcherObject");
|
||||
DataWatcherHelper.DataWatcherRegistry = getLegacyClass("DataWatcherRegistry");
|
||||
DataWatcherHelper.DataWatcherSerializer = getLegacyClass("DataWatcherSerializer");
|
||||
PacketScoreboard.EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* NMS loader for minecraft 1.17+ using Mojang packaging and bukkit names.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class BukkitModernNMSStorage extends NMSStorage {
|
||||
|
||||
@Override
|
||||
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
|
||||
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
|
||||
DataWatcher.REGISTER = ReflectionUtils.getMethod(DataWatcher.CLASS, new String[]{"register", "a"}, DataWatcherObject.CLASS, Object.class); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardScore_setScore = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardScoreClass, new String[]{"setScore", "b"}, int.class); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setAllowFriendlyFire", "a"}, boolean.class); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setCanSeeFriendlyInvisibles", "b"}, boolean.class); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setPrefix", "b"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setSuffix", "c"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
|
||||
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setNameTagVisibility", "a"}, PacketScoreboard.EnumNameTagVisibility); // {Bukkit, Bukkit 1.18+}
|
||||
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("a").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("c").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
|
||||
if (is1_19_3Plus()) {
|
||||
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("g").get(null);
|
||||
if (is1_19_4Plus()) {
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("k").get(null);
|
||||
} else {
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("j").get(null);
|
||||
}
|
||||
} else {
|
||||
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
|
||||
}
|
||||
if (minorVersion >= 19) {
|
||||
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("d").get(null);
|
||||
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("e")).setAccessible(true);
|
||||
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("b");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadClasses() throws ClassNotFoundException {
|
||||
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
|
||||
World = Class.forName("net.minecraft.world.level.World");
|
||||
EntityArmorStand = Class.forName("net.minecraft.world.entity.decoration.EntityArmorStand");
|
||||
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
|
||||
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
|
||||
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
|
||||
Packet = Class.forName("net.minecraft.network.protocol.Packet");
|
||||
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
|
||||
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
|
||||
Entity = Class.forName("net.minecraft.world.entity.Entity");
|
||||
EntityLiving = Class.forName("net.minecraft.world.entity.EntityLiving");
|
||||
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
|
||||
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
|
||||
PacketScoreboard.DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
|
||||
PacketScoreboard.ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
|
||||
PacketScoreboard.Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
|
||||
PacketScoreboard.ScorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
|
||||
PacketScoreboard.ScoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
|
||||
PacketScoreboard.ScoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
|
||||
PacketScoreboard.IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
|
||||
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
|
||||
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
|
||||
PacketScoreboard.TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
|
||||
PacketScoreboard.ScoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
|
||||
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
|
||||
PacketScoreboard.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
|
||||
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher");
|
||||
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher$Item");
|
||||
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
|
||||
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
|
||||
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
|
||||
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
|
||||
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity");
|
||||
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
|
||||
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook");
|
||||
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
|
||||
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn");
|
||||
BukkitTabList.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
|
||||
if (minorVersion >= 19) {
|
||||
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
|
||||
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
|
||||
} else {
|
||||
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving");
|
||||
}
|
||||
if (is1_19_3Plus()) {
|
||||
BukkitTabList.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
|
||||
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
|
||||
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
|
||||
BukkitTabList.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
|
||||
} else {
|
||||
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
|
||||
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
|
||||
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
|
||||
/**
|
||||
* NMS loader for minecraft 1.17+ using Mojang packaging and names.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class MojangModernNMSStorage extends NMSStorage {
|
||||
|
||||
@Override
|
||||
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
|
||||
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("fromJson", String.class);
|
||||
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("define", DataWatcherObject.CLASS, Object.class);
|
||||
PacketScoreboard.ScoreboardScore_setScore = PacketScoreboard.ScoreboardScoreClass.getMethod("setScore", int.class);
|
||||
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = PacketScoreboard.ScoreboardTeam.getMethod("setAllowFriendlyFire", boolean.class);
|
||||
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = PacketScoreboard.ScoreboardTeam.getMethod("setSeeFriendlyInvisibles", boolean.class);
|
||||
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("setPlayerPrefix", IChatBaseComponent);
|
||||
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("setPlayerSuffix", IChatBaseComponent);
|
||||
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = PacketScoreboard.ScoreboardTeam.getMethod("setNameTagVisibility", PacketScoreboard.EnumNameTagVisibility);
|
||||
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BYTE").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("FLOAT").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("STRING").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("OPTIONAL_COMPONENT").get(null);
|
||||
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BOOLEAN").get(null);
|
||||
if (minorVersion >= 19) {
|
||||
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
|
||||
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("type")).setAccessible(true);
|
||||
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("packDirty");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadClasses() throws ClassNotFoundException {
|
||||
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
|
||||
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
|
||||
World = Class.forName("net.minecraft.world.level.Level");
|
||||
EntityArmorStand = Class.forName("net.minecraft.world.entity.decoration.ArmorStand");
|
||||
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
|
||||
NetworkManager = Class.forName("net.minecraft.network.Connection");
|
||||
Packet = Class.forName("net.minecraft.network.protocol.Packet");
|
||||
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
|
||||
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
|
||||
Entity = Class.forName("net.minecraft.world.entity.Entity");
|
||||
EntityLiving = Class.forName("net.minecraft.world.entity.LivingEntity");
|
||||
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
|
||||
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
|
||||
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
|
||||
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData$DataItem");
|
||||
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
|
||||
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
|
||||
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
|
||||
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket");
|
||||
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket");
|
||||
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket");
|
||||
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot");
|
||||
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket");
|
||||
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket");
|
||||
BukkitTabList.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
|
||||
PacketScoreboard.DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
|
||||
PacketScoreboard.ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
|
||||
PacketScoreboard.Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
|
||||
PacketScoreboard.ScorePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
|
||||
PacketScoreboard.ScoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
|
||||
PacketScoreboard.ScoreboardScoreClass = Class.forName("net.minecraft.world.scores.Score");
|
||||
PacketScoreboard.IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
|
||||
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
|
||||
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
|
||||
PacketScoreboard.TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
|
||||
PacketScoreboard.ScoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
|
||||
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
|
||||
PacketScoreboard.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
|
||||
if (minorVersion >= 19) {
|
||||
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
|
||||
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket");
|
||||
} else {
|
||||
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddMobPacket");
|
||||
}
|
||||
if (is1_19_3Plus()) {
|
||||
BukkitTabList.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
|
||||
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
|
||||
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
|
||||
BukkitTabList.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
|
||||
} else {
|
||||
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
|
||||
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
|
||||
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ComponentCache;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class holding all NMS classes, methods, fields and constructors used by TAB.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public abstract class NMSStorage {
|
||||
|
||||
/** Instance of this class */
|
||||
@Getter @Setter private static NMSStorage instance;
|
||||
|
||||
/** Server's NMS/CraftBukkit package */
|
||||
@Getter protected final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
|
||||
/** Server's minor version */
|
||||
@Getter protected final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
|
||||
/** Flag determining whether the server version is at least 1.19.3 or not */
|
||||
@Getter private final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
@Getter private final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
|
||||
|
||||
/** Basic universal values */
|
||||
protected Class<?> Packet;
|
||||
protected Class<?> NetworkManager;
|
||||
protected Class<?> EntityArmorStand;
|
||||
protected Class<?> World;
|
||||
public Class<Enum> EnumChatFormat;
|
||||
public Class<?> EntityPlayer;
|
||||
protected Class<?> EntityHuman;
|
||||
public Class<?> Entity;
|
||||
public Class<?> EntityLiving;
|
||||
protected Class<?> PlayerConnection;
|
||||
|
||||
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
|
||||
public Field PING;
|
||||
|
||||
public Field PLAYER_CONNECTION;
|
||||
public Field NETWORK_MANAGER;
|
||||
public Field CHANNEL;
|
||||
public Method getHandle;
|
||||
public Method sendPacket;
|
||||
public Method getProfile;
|
||||
|
||||
/** Chat components */
|
||||
public Class<?> IChatBaseComponent;
|
||||
protected Class<?> ChatSerializer;
|
||||
public Method ChatSerializer_DESERIALIZE;
|
||||
|
||||
/** Other entity packets */
|
||||
public Class<?> PacketPlayOutEntity;
|
||||
public Field PacketPlayOutEntity_ENTITYID;
|
||||
|
||||
public Class<?> PacketPlayOutEntityLook;
|
||||
|
||||
public Class<?> PacketPlayOutNamedEntitySpawn;
|
||||
public Field PacketPlayOutNamedEntitySpawn_ENTITYID;
|
||||
|
||||
public Object dummyEntity;
|
||||
|
||||
private final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(10000,
|
||||
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load all fields, methods and constructors.
|
||||
*/
|
||||
@SneakyThrows
|
||||
public NMSStorage() {
|
||||
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
|
||||
loadClasses();
|
||||
if (minorVersion >= 7) {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
|
||||
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
|
||||
} else {
|
||||
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
|
||||
Constructor<?> newEntityArmorStand = EntityArmorStand.getConstructor(World, double.class, double.class, double.class);
|
||||
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".CraftWorld").getMethod("getHandle");
|
||||
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
|
||||
BukkitTabList.load(this);
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcherObject.load();
|
||||
}
|
||||
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
|
||||
DataWatcher.load(this);
|
||||
DataWatcherItem.load(this);
|
||||
PacketPlayOutEntityDestroyStorage.load();
|
||||
PacketPlayOutEntityMetadataStorage.load(this);
|
||||
PacketPlayOutEntityTeleportStorage.load(this);
|
||||
PacketPlayOutSpawnEntityLivingStorage.load(this);
|
||||
PacketScoreboard.load(this);
|
||||
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
|
||||
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
|
||||
loadNamedFieldsAndMethods();
|
||||
if (minorVersion < 17) {
|
||||
try {
|
||||
(PING = EntityPlayer.getDeclaredField("ping")).setAccessible(true); // 1.5.2 - 1.16.5
|
||||
} catch (NoSuchFieldException e) {
|
||||
(PING = EntityPlayer.getDeclaredField("field_71138_i")).setAccessible(true); // 1.7.10 Thermos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all classes used by the plugin
|
||||
*
|
||||
* @throws ClassNotFoundException
|
||||
* If a class was not found
|
||||
*/
|
||||
public abstract void loadClasses() throws ClassNotFoundException;
|
||||
|
||||
/**
|
||||
* Loads all fields and methods which must be loaded by their name due to
|
||||
* lack of other methods of reliably retrieving them.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If a field or method was not found
|
||||
*/
|
||||
public abstract void loadNamedFieldsAndMethods() throws ReflectiveOperationException;
|
||||
|
||||
/**
|
||||
* Converts TAB's IChatBaseComponent into minecraft's component using String deserialization.
|
||||
* If the requested component is found in cache, it is returned. If not, it is created, added into cache and returned.
|
||||
* If {@code component} is {@code null}, returns {@code null}
|
||||
*
|
||||
* @param component
|
||||
* component to convert
|
||||
* @param clientVersion
|
||||
* client version used to decide RGB conversion
|
||||
* @return converted component or {@code null} if {@code component} is {@code null}
|
||||
*/
|
||||
public @Nullable Object toNMSComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
|
||||
return componentCache.get(component, clientVersion);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* NMS loader for Thermos 1.7.10.
|
||||
*/
|
||||
public class ThermosNMSStorage extends BukkitLegacyNMSStorage {
|
||||
|
||||
@Override
|
||||
public Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
|
||||
try {
|
||||
return getClass().getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
} catch (NullPointerException e) {
|
||||
// nested class not found
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
|
||||
PacketScoreboard.ScoreboardScore_setScore = PacketScoreboard.ScoreboardScoreClass.getMethod("func_96647_c", int.class);
|
||||
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = PacketScoreboard.ScoreboardTeam.getMethod("func_96660_a", boolean.class);
|
||||
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = PacketScoreboard.ScoreboardTeam.getMethod("func_98300_b", boolean.class);
|
||||
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("func_75682_a", int.class, Object.class);
|
||||
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("func_96666_b", String.class);
|
||||
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("func_96662_c", String.class);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
|
||||
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutEntityDestroyStorage {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
public static Field ENTITIES;
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @throws NoSuchMethodException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load() throws NoSuchMethodException {
|
||||
ENTITIES = ReflectionUtils.getOnlyField(CLASS);
|
||||
try {
|
||||
CONSTRUCTOR = CLASS.getConstructor(int[].class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
//1.17.0
|
||||
CONSTRUCTOR = CLASS.getConstructor(int.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutEntityMetadataStorage {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @param nms
|
||||
* NMS storage reference
|
||||
* @throws NoSuchMethodException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load(NMSStorage nms) throws NoSuchMethodException {
|
||||
if (nms.is1_19_3Plus()) {
|
||||
CONSTRUCTOR = CLASS.getConstructor(int.class, List.class);
|
||||
} else {
|
||||
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcher.CLASS, boolean.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutEntityTeleportStorage {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
public static Field ENTITY_ID;
|
||||
public static Field X;
|
||||
public static Field Y;
|
||||
public static Field Z;
|
||||
public static Field YAW;
|
||||
public static Field PITCH;
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @param nms
|
||||
* NMS storage reference
|
||||
* @throws NoSuchMethodException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load(NMSStorage nms) throws NoSuchMethodException {
|
||||
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
|
||||
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
|
||||
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
CONSTRUCTOR = CLASS.getConstructor(nms.Entity);
|
||||
} else {
|
||||
CONSTRUCTOR = CLASS.getConstructor();
|
||||
}
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
|
||||
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
|
||||
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
|
||||
} else {
|
||||
X = ReflectionUtils.getFields(CLASS, int.class).get(1);
|
||||
Y = ReflectionUtils.getFields(CLASS, int.class).get(2);
|
||||
Z = ReflectionUtils.getFields(CLASS, int.class).get(3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this class into NMS packet
|
||||
*
|
||||
* @return NMS packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Object build(int entityId, Location location) {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object nmsPacket;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
|
||||
} else {
|
||||
nmsPacket = CONSTRUCTOR.newInstance();
|
||||
}
|
||||
ENTITY_ID.set(nmsPacket, entityId);
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
X.set(nmsPacket, location.getX());
|
||||
Y.set(nmsPacket, location.getY());
|
||||
Z.set(nmsPacket, location.getZ());
|
||||
} else {
|
||||
X.set(nmsPacket, floor(location.getX()*32));
|
||||
Y.set(nmsPacket, floor(location.getY()*32));
|
||||
Z.set(nmsPacket, floor(location.getZ()*32));
|
||||
}
|
||||
YAW.set(nmsPacket, (byte) (location.getYaw()/360*256));
|
||||
PITCH.set(nmsPacket, (byte) (location.getPitch()/360*256));
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
private static int floor(double paramDouble) {
|
||||
int i = (int)paramDouble;
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.EnumMap;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
|
||||
*/
|
||||
public class PacketPlayOutSpawnEntityLivingStorage {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> CLASS;
|
||||
public static Class<?> EntityTypes;
|
||||
public static Constructor<?> CONSTRUCTOR;
|
||||
public static Field ENTITY_ID;
|
||||
public static Field ENTITY_TYPE;
|
||||
public static Field YAW;
|
||||
public static Field PITCH;
|
||||
public static Field UUID;
|
||||
public static Field X;
|
||||
public static Field Y;
|
||||
public static Field Z;
|
||||
public static Field DATA_WATCHER;
|
||||
public static Object EntityTypes_ARMOR_STAND;
|
||||
|
||||
/** Entity types */
|
||||
public static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*
|
||||
* @param nms
|
||||
* NMS storage reference
|
||||
* @throws NoSuchMethodException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load(NMSStorage nms) throws NoSuchMethodException {
|
||||
if (nms.getMinorVersion() >= 19) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 2);
|
||||
} else if (nms.getMinorVersion() >= 13) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 1);
|
||||
} else {
|
||||
entityIds.put(EntityType.WITHER, 64);
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 30);
|
||||
}
|
||||
}
|
||||
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
CONSTRUCTOR = CLASS.getConstructor(nms.is1_19_3Plus() ? nms.Entity : nms.EntityLiving);
|
||||
} else {
|
||||
CONSTRUCTOR = CLASS.getConstructor();
|
||||
}
|
||||
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
|
||||
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
|
||||
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
UUID = ReflectionUtils.getOnlyField(CLASS, UUID.class);
|
||||
if (nms.getMinorVersion() >= 19) {
|
||||
X = ReflectionUtils.getFields(CLASS, double.class).get(2);
|
||||
Y = ReflectionUtils.getFields(CLASS, double.class).get(3);
|
||||
Z = ReflectionUtils.getFields(CLASS, double.class).get(4);
|
||||
} else {
|
||||
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
|
||||
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
|
||||
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
|
||||
}
|
||||
} else {
|
||||
X = ReflectionUtils.getFields(CLASS, int.class).get(2);
|
||||
Y = ReflectionUtils.getFields(CLASS, int.class).get(3);
|
||||
Z = ReflectionUtils.getFields(CLASS, int.class).get(4);
|
||||
}
|
||||
if (nms.getMinorVersion() < 19) {
|
||||
ENTITY_TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
|
||||
}
|
||||
if (nms.getMinorVersion() <= 14) {
|
||||
DATA_WATCHER = ReflectionUtils.getOnlyField(CLASS, DataWatcher.CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this class into NMS packet
|
||||
*
|
||||
* @return NMS packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Object build(int entityId, UUID id, Object entityType, Location location, EntityData data) {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
Object nmsPacket;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
|
||||
} else {
|
||||
nmsPacket = CONSTRUCTOR.newInstance();
|
||||
}
|
||||
ENTITY_ID.set(nmsPacket, entityId);
|
||||
YAW.set(nmsPacket, (byte)(location.getYaw() * 256.0f / 360.0f));
|
||||
PITCH.set(nmsPacket, (byte)(location.getPitch() * 256.0f / 360.0f));
|
||||
if (nms.getMinorVersion() <= 14) {
|
||||
DATA_WATCHER.set(nmsPacket, data.build());
|
||||
}
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
UUID.set(nmsPacket, id);
|
||||
X.set(nmsPacket, location.getX());
|
||||
Y.set(nmsPacket, location.getY());
|
||||
Z.set(nmsPacket, location.getZ());
|
||||
} else {
|
||||
X.set(nmsPacket, floor(location.getX()*32));
|
||||
Y.set(nmsPacket, floor(location.getY()*32));
|
||||
Z.set(nmsPacket, floor(location.getZ()*32));
|
||||
}
|
||||
if (nms.getMinorVersion() >= 19) {
|
||||
ENTITY_TYPE.set(nmsPacket, EntityTypes_ARMOR_STAND); // :(
|
||||
} else {
|
||||
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) entityType));
|
||||
}
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
private static int floor(double paramDouble) {
|
||||
int i = (int)paramDouble;
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation for Bukkit, which uses packets
|
||||
* to send scoreboards. While md_5 keeps saying that you don't
|
||||
* need packets for scoreboards in every single spigot update,
|
||||
* this is not true. It may seem that way at first, but after
|
||||
* investigating, fatal problems are found:<p>
|
||||
* #1 - Limitations on legacy versions are forced in the API.
|
||||
* While this may not seem like a problem, it enforces those
|
||||
* limits even for 1.13+ players (if using ViaVersion).<p>
|
||||
* #2 - Modern versions no longer have any limits, but md_5
|
||||
* decided to add some random limits for absolutely no reason
|
||||
* at all. Scoreboard title received a random 128 characters
|
||||
* limit including color codes. Together with the almighty bukkit
|
||||
* RGB format using 14 characters for 1 color code, this makes
|
||||
* gradients just impossible to use. Team prefix/suffix also
|
||||
* received a 64 characters limit (excluding color codes at least),
|
||||
* however that might not be enough for displaying a line of text
|
||||
* in sidebar, which would require splitting the text into prefix
|
||||
* and suffix, which is just begging for bugs to be introduced.<p>
|
||||
* #3 - Other plugins can decide to put players into their own
|
||||
* scoreboard, automatically destroying all visuals made by the
|
||||
* plugin. They might also put all players into the same scoreboard,
|
||||
* making per-player view of teams, especially sidebar not working.<p>
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static NMSStorage nms;
|
||||
|
||||
public static Class<?> Scoreboard;
|
||||
public static Class<?> IScoreboardCriteria;
|
||||
private static Object emptyScoreboard;
|
||||
private static Field IScoreboardCriteria_self;
|
||||
|
||||
// PacketPlayOutScoreboardDisplayObjective
|
||||
public static Class<?> DisplayObjectiveClass;
|
||||
private static Constructor<?> newDisplayObjective;
|
||||
public static Field DisplayObjective_POSITION;
|
||||
public static Field DisplayObjective_OBJECTIVE_NAME;
|
||||
|
||||
// PacketPlayOutScoreboardScore
|
||||
public static Class<?> ScorePacketClass;
|
||||
public static Class<?> ScoreboardScoreClass;
|
||||
public static Class<Enum> EnumScoreboardAction;
|
||||
private static Constructor<?> newScorePacket_1_13;
|
||||
private static Constructor<?> newScorePacket_String;
|
||||
private static Constructor<?> newScorePacket;
|
||||
private static Constructor<?> newScoreboardScore;
|
||||
public static Method ScoreboardScore_setScore;
|
||||
|
||||
// PacketPlayOutScoreboardObjective
|
||||
public static Class<?> ObjectivePacketClass;
|
||||
private static Constructor<?> newObjectivePacket;
|
||||
public static Field Objective_OBJECTIVE_NAME;
|
||||
public static Field Objective_METHOD;
|
||||
private static Field Objective_RENDER_TYPE;
|
||||
private static Field Objective_DISPLAY_NAME;
|
||||
public static Class<Enum> EnumScoreboardHealthDisplay;
|
||||
public static Class<?> ScoreboardObjective;
|
||||
private static Constructor<?> newScoreboardObjective;
|
||||
|
||||
// PacketPlayOutScoreboardTeamStorage
|
||||
public static Class<?> TeamPacketClass;
|
||||
private static Constructor<?> newTeamPacket;
|
||||
private static Method TeamPacketConstructor_of;
|
||||
private static Method TeamPacketConstructor_ofBoolean;
|
||||
public static Field TeamPacket_NAME;
|
||||
public static Field TeamPacket_ACTION;
|
||||
public static Field TeamPacket_PLAYERS;
|
||||
public static Class<Enum> EnumNameTagVisibility;
|
||||
public static Class<Enum> EnumTeamPush;
|
||||
public static Class<?> ScoreboardTeam;
|
||||
private static Constructor<?> newScoreboardTeam;
|
||||
private static Method ScoreboardTeam_getPlayerNameSet;
|
||||
public static Method ScoreboardTeam_setNameTagVisibility;
|
||||
private static Method ScoreboardTeam_setCollisionRule;
|
||||
public static Method ScoreboardTeam_setPrefix;
|
||||
public static Method ScoreboardTeam_setSuffix;
|
||||
private static Method ScoreboardTeam_setColor;
|
||||
public static Method ScoreboardTeam_setAllowFriendlyFire;
|
||||
public static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
|
||||
|
||||
public static void load(NMSStorage nms) throws ReflectiveOperationException {
|
||||
PacketScoreboard.nms = nms;
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
IScoreboardCriteria_self = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
|
||||
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
|
||||
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
|
||||
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
|
||||
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
|
||||
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
|
||||
Objective_METHOD = list.get(list.size()-1);
|
||||
newScoreboardScore = ScoreboardScoreClass.getConstructor(Scoreboard, ScoreboardObjective, String.class);
|
||||
newScoreboardTeam = ScoreboardTeam.getConstructor(Scoreboard, String.class);
|
||||
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
|
||||
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
|
||||
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
|
||||
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(ScoreboardTeam, Collection.class);
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
newScorePacket_1_13 = ScorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
|
||||
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, nms.IChatBaseComponent);
|
||||
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, nms.EnumChatFormat);
|
||||
} else {
|
||||
newScorePacket_String = ScorePacketClass.getConstructor(String.class);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor();
|
||||
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass);
|
||||
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
|
||||
} else {
|
||||
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass, int.class);
|
||||
}
|
||||
}
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, EnumTeamPush);
|
||||
}
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam);
|
||||
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam, boolean.class);
|
||||
} else {
|
||||
newTeamPacket = TeamPacketClass.getConstructor(ScoreboardTeam, int.class);
|
||||
}
|
||||
}
|
||||
|
||||
public PacketScoreboard(BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
player.sendPacket(newDisplayObjective.newInstance(slot.ordinal(), newScoreboardObjective(objective)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
player.sendPacket(buildObjective(0, objectiveName, title, hearts));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
player.sendPacket(buildObjective(1, objectiveName, "", false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
player.sendPacket(buildObjective(2, objectiveName, title, hearts));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object buildObjective(int action, String objectiveName, String title, boolean hearts) {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return newObjectivePacket.newInstance(
|
||||
newScoreboardObjective.newInstance(null, objectiveName, null,
|
||||
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
|
||||
asDisplayType(hearts)),
|
||||
action
|
||||
);
|
||||
}
|
||||
Object nmsPacket = newObjectivePacket.newInstance();
|
||||
Objective_OBJECTIVE_NAME.set(nmsPacket, objectiveName);
|
||||
Objective_DISPLAY_NAME.set(nmsPacket, title);
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
Objective_RENDER_TYPE.set(nmsPacket, asDisplayType(hearts));
|
||||
}
|
||||
Objective_METHOD.set(nmsPacket, action);
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
private Object asDisplayType(boolean hearts) {
|
||||
return Enum.valueOf(EnumScoreboardHealthDisplay, hearts ? "HEARTS" : "INTEGER");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
|
||||
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, true));
|
||||
} else {
|
||||
player.sendPacket(newTeamPacket.newInstance(team, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
player.sendPacket(TeamPacketConstructor_of.invoke(null, team));
|
||||
} else {
|
||||
player.sendPacket(newTeamPacket.newInstance(team, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
|
||||
} else {
|
||||
player.sendPacket(newTeamPacket.newInstance(team, 2));
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createTeam(String teamName, String prefix, String suffix, NameVisibility visibility, CollisionRule collision, int options) {
|
||||
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
|
||||
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
ScoreboardTeam_setPrefix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
|
||||
ScoreboardTeam_setSuffix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
|
||||
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, EnumChatFormat.lastColorsOf(prefix).toString()));
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix.invoke(team, prefix);
|
||||
ScoreboardTeam_setSuffix.invoke(team, suffix);
|
||||
}
|
||||
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
|
||||
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
|
||||
return team;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
Object packet;
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
packet = newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score);
|
||||
} else {
|
||||
Object scoreboardScore = newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(objective), playerName);
|
||||
ScoreboardScore_setScore.invoke(scoreboardScore, score);
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
packet = newScorePacket.newInstance(scoreboardScore);
|
||||
} else {
|
||||
packet = newScorePacket.newInstance(scoreboardScore, 0);
|
||||
}
|
||||
}
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, playerName, 0));
|
||||
} else {
|
||||
player.sendPacket(newScorePacket_String.newInstance(playerName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Scoreboard Objective with given name.
|
||||
*
|
||||
* @param objectiveName
|
||||
* Objective name
|
||||
* @return NMS Objective
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object newScoreboardObjective(@NotNull String objectiveName) {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return newScoreboardObjective.newInstance(
|
||||
null,
|
||||
objectiveName,
|
||||
null,
|
||||
nms.toNMSComponent(new IChatBaseComponent(""), TAB.getInstance().getServerVersion()),
|
||||
null
|
||||
);
|
||||
}
|
||||
return newScoreboardObjective.newInstance(
|
||||
null,
|
||||
objectiveName,
|
||||
IScoreboardCriteria_self.get(null)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.scoreboard.Objective;
|
||||
import org.bukkit.scoreboard.RenderType;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Scoreboard handler using Paper API, which got added
|
||||
* in early 1.16.5 builds. Unlike the Bukkit API, the new
|
||||
* methods using adventure components do not have any
|
||||
* pointless artificial limits added in.
|
||||
* <p>
|
||||
* However, it still inherits the issue with compatibility
|
||||
* with other plugins that create scoreboards.
|
||||
* <p>
|
||||
* Currently, this class is never used. In the future, it might
|
||||
* be used as a fallback solution for unsupported versions if NMS
|
||||
* changes and fields fail to load. Or as a complete replacement
|
||||
* if compatibility with other plugins turns out not to be a problem.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions") // Throw the NPE if something is not as expected, parent class should ensure it anyway
|
||||
public class PaperScoreboard extends Scoreboard<BukkitTabPlayer> {
|
||||
|
||||
private final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
|
||||
|
||||
public PaperScoreboard(BukkitTabPlayer player) {
|
||||
super(player);
|
||||
|
||||
// Put player into a different scoreboard for per-player view
|
||||
player.getPlayer().setScoreboard(scoreboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getObjective(objective).setDisplaySlot(org.bukkit.scoreboard.DisplaySlot.valueOf(slot.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getObjective(objective).getScore(playerName).setScore(score);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.resetScores(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.registerNewObjective(objectiveName, "dummy", toAdventure(title), hearts ? RenderType.HEARTS : RenderType.INTEGER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getObjective(objectiveName).unregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
checkPlayerScoreboard();
|
||||
Objective obj = scoreboard.getObjective(objectiveName);
|
||||
obj.setRenderType(hearts ? RenderType.HEARTS : RenderType.INTEGER);
|
||||
obj.displayName(toAdventure(title));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
checkPlayerScoreboard();
|
||||
Team team = scoreboard.registerNewTeam(name);
|
||||
team.prefix(toAdventure(prefix));
|
||||
team.suffix(toAdventure(suffix));
|
||||
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.values()[visibility.ordinal()]);
|
||||
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
|
||||
players.forEach(team::addEntry);
|
||||
team.setAllowFriendlyFire((options & 0x01) != 0);
|
||||
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getTeam(name).unregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
checkPlayerScoreboard();
|
||||
Team team = scoreboard.getTeam(name);
|
||||
team.prefix(toAdventure(prefix));
|
||||
team.suffix(toAdventure(suffix));
|
||||
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.values()[visibility.ordinal()]);
|
||||
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
|
||||
team.setAllowFriendlyFire((options & 0x01) != 0);
|
||||
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts raw text into adventure component
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @return Converted component
|
||||
*/
|
||||
private @NotNull Component toAdventure(@NotNull String text) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toAdventureComponent(player.getVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure player is in the correct scoreboard.
|
||||
* If not, puts the player into correct scoreboard and prints
|
||||
* a warning.
|
||||
* The only possible reason for this to happen is another plugin
|
||||
* putting a player into a different scoreboard.
|
||||
* Sadly there is no efficient solution to this, which md_5 fails
|
||||
* to understand and keeps saying you don't need packets for scoreboards.
|
||||
*/
|
||||
private void checkPlayerScoreboard() {
|
||||
if (player.getPlayer().getScoreboard() != scoreboard) {
|
||||
player.getPlayer().setScoreboard(scoreboard);
|
||||
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
|
||||
"than expected. This means another plugin changed player's scoreboard.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
main: me.neznamy.tab.platforms.bukkit.BukkitTAB
|
||||
name: @name@
|
||||
version: @version@
|
||||
description: @description@
|
||||
api-version: 1.13 # Defining to avoid legacy plugin warn
|
||||
load: POSTWORLD
|
||||
author: @author@
|
||||
website: "@website@"
|
||||
depend: []
|
||||
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate]
|
||||
loadbefore: []
|
||||
prefix: TAB
|
||||
libraries: []
|
||||
folia-supported: false
|
||||
commands:
|
||||
tab:
|
||||
description: Plugin's main command
|
||||
permissions:
|
||||
tab.admin:
|
||||
description: Grants all TAB permissions
|
||||
default: op
|
||||
tab.tabcomplete:
|
||||
description: Allows player to complete all TAB commands
|
||||
default: op
|
||||
tab.reload:
|
||||
description: Allows player to run /tab reload
|
||||
default: op
|
||||
tab.cpu:
|
||||
description: Allows player to run /tab cpu
|
||||
default: op
|
||||
tab.debug:
|
||||
description: Allows player to run /tab debug [player]
|
||||
default: op
|
||||
tab.groupinfo:
|
||||
description: Allows player to run /tab group <group>
|
||||
default: op
|
||||
tab.grouplist:
|
||||
description: Allows player to run /tab groups
|
||||
default: op
|
||||
tab.parse:
|
||||
description: Allows player to run /tab parse <player> <placeholder>
|
||||
default: op
|
||||
tab.setcollision:
|
||||
description: Allows player to run /tab setcollision <player> <true/false>
|
||||
default: op
|
||||
tab.remove:
|
||||
description: Allows player to remove group/user using commands
|
||||
default: op
|
||||
tab.change.*:
|
||||
description: Allows player to change property using command
|
||||
default: op
|
||||
tab.width:
|
||||
description: Allows player to run /tab width
|
||||
default: op
|
||||
tab.announce.bar:
|
||||
description: Allows player to run /tab announce bar
|
||||
default: op
|
||||
tab.bossbar.toggle:
|
||||
description: Allows player to run /tab bossbar
|
||||
default: op
|
||||
tab.send.bar:
|
||||
description: Allows player to run /tab bossbar send
|
||||
default: op
|
||||
tab.mysql.download:
|
||||
description: Allows player to run /tab mysql download
|
||||
default: op
|
||||
tab.mysql.upload:
|
||||
description: Allows player to run /tab mysql upload
|
||||
default: op
|
||||
tab.nametag.toggle:
|
||||
description: Allows player to run /tab nametag toggle
|
||||
default: op
|
||||
tab.nametag.toggle.other:
|
||||
description: Allows player to run /tab nametag toggle [other player]
|
||||
default: op
|
||||
tab.nametag.preview:
|
||||
description: Allows player to run /tab nametag preview
|
||||
default: op
|
||||
tab.nametag.preview.other:
|
||||
description: Allows player to run /tab nametag preview [other player]
|
||||
default: op
|
||||
tab.announce.scoreboard:
|
||||
description: Allows player to run /tab scoreboard announce
|
||||
default: op
|
||||
tab.scoreboard.toggle:
|
||||
description: Allows player to run /tab scoreboard
|
||||
default: op
|
||||
tab.scoreboard.toggle.other:
|
||||
description: Allows player to run /tab scoreboard for other players
|
||||
default: op
|
||||
tab.scoreboard.show:
|
||||
description: Allows player to run /tab scoreboard show
|
||||
default: op
|
||||
tab.scoreboard.show.other:
|
||||
description: Allows player to run /tab scoreboard show for other players
|
||||
default: op
|
||||
tab.staff:
|
||||
description: Makes player counted in %staffonline% placeholder
|
||||
default: op
|
||||
tab.group.*:
|
||||
description: Considers player in group if assigning by permissions
|
||||
default: op
|
||||
tab.bypass:
|
||||
description: Makes player bypass per world playerlist if bypass permission is enabled
|
||||
default: op
|
||||
tab.seevanished:
|
||||
description: Marks player as able to see vanished players for features hooking into vanish plugins
|
||||
default: op
|
||||
tab.spectatorbypass:
|
||||
description: Makes player bypass spectator fix
|
||||
default: op
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bungeecord:3.0.1")
|
||||
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.1-SNAPSHOT")
|
||||
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
|
||||
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* BossBar handler for BungeeCord. It uses packets, since
|
||||
* BungeeCord does not have a BossBar API. Only supports
|
||||
* 1.9+ players, as dealing with entities would be simply impossible.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeBossBar implements BossBar {
|
||||
|
||||
private final @NotNull BungeeTabPlayer player;
|
||||
|
||||
/** Both are included in the same action, need to remember and write them both */
|
||||
private final Map<UUID, Integer> colors = new HashMap<>();
|
||||
private final Map<UUID, Integer> styles = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 0);
|
||||
packet.setHealth(progress);
|
||||
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
|
||||
packet.setColor(color.ordinal());
|
||||
packet.setDivision(style.ordinal());
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
|
||||
colors.put(id, color.ordinal());
|
||||
styles.put(id, style.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 3);
|
||||
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 2);
|
||||
packet.setHealth(progress);
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
|
||||
packet.setDivision(style.ordinal());
|
||||
packet.setColor(colors.get(id));
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
|
||||
styles.put(id, style.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
|
||||
packet.setDivision(styles.get(id));
|
||||
packet.setColor(color.ordinal());
|
||||
player.getPlayer().unsafe().sendPacket(packet);
|
||||
|
||||
colors.put(id, color.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
player.getPlayer().unsafe().sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
|
||||
|
||||
colors.remove(id);
|
||||
styles.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.ChatEvent;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerSwitchEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
/**
|
||||
* The core for BungeeCord forwarding events into all enabled features
|
||||
*/
|
||||
public class BungeeEventListener extends EventListener<ProxiedPlayer> implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerDisconnectEvent e) {
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onSwitch(ServerSwitchEvent e) {
|
||||
serverChange(e.getPlayer(), e.getPlayer().getUniqueId(), e.getPlayer().getServer().getInfo().getName());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChat(ChatEvent e) {
|
||||
if (e.isCommand() && command(((ProxiedPlayer)e.getSender()).getUniqueId(), e.getMessage())) {
|
||||
e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(PluginMessageEvent event) {
|
||||
if (!event.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
|
||||
if (event.getReceiver() instanceof ProxiedPlayer) {
|
||||
event.setCancelled(true);
|
||||
pluginMessage(((ProxiedPlayer) event.getReceiver()).getUniqueId(), event.getData());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabPlayer createPlayer(ProxiedPlayer player) {
|
||||
return new BungeeTabPlayer(player);
|
||||
}
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.features.redis.feature.RedisTeams;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.redis.RedisPlayer;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.packet.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Pipeline injection to secure proper functionality
|
||||
* of some features by preventing other plugins
|
||||
* from overriding it.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
private static @Nullable Field wrapperField;
|
||||
|
||||
static {
|
||||
try {
|
||||
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/** Packets used by the plugin that must be deserialized and BungeeCord does not do it automatically */
|
||||
private final Class<? extends DefinedPacket>[] extraPacketClasses = new Class[]{Team.class, ScoreboardDisplay.class, ScoreboardObjective.class};
|
||||
private final Supplier<DefinedPacket>[] extraPacketSuppliers = new Supplier[]{Team::new, ScoreboardDisplay::new, ScoreboardObjective::new};
|
||||
|
||||
@Override
|
||||
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
|
||||
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : TabChannelDuplexHandler::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Channel getChannel(@NotNull TabPlayer player) {
|
||||
if (wrapperField == null) return null;
|
||||
final BungeeTabPlayer bungee = (BungeeTabPlayer) player;
|
||||
try {
|
||||
return ((ChannelWrapper) wrapperField.get(bungee.getPlayer().getPendingConnection())).getHandle();
|
||||
} catch (final IllegalAccessException exception) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
|
||||
((ScoreboardDisplay) packet).getPosition(), ((ScoreboardDisplay) packet).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
((ScoreboardObjective) packet).getAction(), ((ScoreboardObjective) packet).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayObjective(@NotNull Object packet) {
|
||||
return packet instanceof ScoreboardDisplay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObjective(@NotNull Object packet) {
|
||||
return packet instanceof ScoreboardObjective;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeam(@NotNull Object packet) {
|
||||
return packet instanceof Team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(@NotNull Object packet) {
|
||||
return packet instanceof PlayerListItem || packet instanceof PlayerListItemUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyPlayers(@NotNull Object team) {
|
||||
if (TAB.getInstance().getNameTagManager() == null) return;
|
||||
Team packet = (Team) team;
|
||||
if (packet.getMode() == 1 || packet.getMode() == 2 || packet.getMode() == 4) return;
|
||||
Collection<String> col = Lists.newArrayList(packet.getPlayers());
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
String expectedTeam = sorting.getShortTeamName(p);
|
||||
if (expectedTeam != null && col.contains(p.getName()) &&
|
||||
!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !packet.getName().equals(expectedTeam)) {
|
||||
logTeamOverride(packet.getName(), p.getName(), expectedTeam);
|
||||
col.remove(p.getName());
|
||||
}
|
||||
}
|
||||
RedisSupport redis = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
|
||||
if (redis != null) {
|
||||
RedisTeams teams = redis.getRedisTeams();
|
||||
if (teams != null) {
|
||||
for (RedisPlayer p : redis.getRedisPlayers().values()) {
|
||||
if (col.contains(p.getName()) && !packet.getName().equals(teams.getTeamNames().get(p))) {
|
||||
logTeamOverride(packet.getName(), p.getName(), teams.getTeamNames().get(p));
|
||||
col.remove(p.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
packet.setPlayers(col.toArray(new String[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
if (packet instanceof PlayerListItemUpdate) {
|
||||
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
|
||||
for (PlayerListItem.Item item : update.getItems()) {
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
|
||||
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PlayerListItem listItem = (PlayerListItem) packet;
|
||||
for (PlayerListItem.Item item : listItem.getItems()) {
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
|
||||
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance of the feature
|
||||
*/
|
||||
public BungeePipelineInjector() {
|
||||
super("inbound-boss");
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel duplex handler override if features using packets that must be
|
||||
* deserialized manually are used. If they are disabled, deserialization is
|
||||
* disabled for better performance.
|
||||
*/
|
||||
public class DeserializableBungeeChannelDuplexHandler extends TabChannelDuplexHandler {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player
|
||||
*
|
||||
* @param player
|
||||
* player to inject
|
||||
*/
|
||||
public DeserializableBungeeChannelDuplexHandler(TabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
|
||||
long time = System.nanoTime();
|
||||
Object modifiedPacket = packet instanceof ByteBuf ? deserialize((ByteBuf) packet) : packet;
|
||||
TAB.getInstance().getCPUManager().addTime(TabConstants.Feature.PACKET_DESERIALIZING, TabConstants.CpuUsageCategory.BYTE_BUF, System.nanoTime()-time);
|
||||
super.write(context, modifiedPacket, channelPromise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes byte buf in case it is one of the tracked packets coming from backend server and returns it.
|
||||
* If the packet is not one of them, returns input
|
||||
*
|
||||
* @param buf
|
||||
* byte buf to deserialize
|
||||
* @return deserialized packet or input byte buf if packet is not tracked
|
||||
*/
|
||||
private @NotNull Object deserialize(@NotNull ByteBuf buf) {
|
||||
int marker = buf.readerIndex();
|
||||
try {
|
||||
int packetId = buf.readByte();
|
||||
for (int i=0; i<extraPacketClasses.length; i++) {
|
||||
if (packetId == ((BungeeTabPlayer)player).getPacketId(extraPacketClasses[i])) {
|
||||
DefinedPacket packet = extraPacketSuppliers[i].get();
|
||||
packet.read(buf, null, ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion());
|
||||
buf.release();
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//rare OverflowPacketException or IndexOutOfBoundsException
|
||||
}
|
||||
buf.readerIndex(marker);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.proxy.ProxyPlatform;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* BungeeCord implementation of Platform
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
@NotNull private final BungeeTAB plugin;
|
||||
|
||||
@Override
|
||||
public @Nullable RedisSupport getRedisSupport() {
|
||||
if (ReflectionUtils.classExists("com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI") &&
|
||||
RedisBungeeAPI.getRedisBungeeApi() != null) {
|
||||
return new RedisBungeeSupport(plugin);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
|
||||
plugin.getLogger().info(message.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerVersionInfo() {
|
||||
return "[BungeeCord] " + plugin.getProxy().getName() + " - " + plugin.getProxy().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||
TAB.getInstance().addPlayer(new BungeeTabPlayer(p));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PipelineInjector createPipelineInjector() {
|
||||
return new BungeePipelineInjector();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardScore;
|
||||
import net.md_5.bungee.protocol.packet.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Scoreboard handler for BungeeCord. Because it does not offer
|
||||
* any Scoreboard API and the scoreboard class it has is just a
|
||||
* downstream tracker, we need to use packets.
|
||||
*/
|
||||
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
|
||||
public BungeeScoreboard(BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
|
||||
player.sendPacket(new ScoreboardDisplay((byte)slot.ordinal(), objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
player.sendPacket(new ScoreboardObjective(objectiveName, "", null, (byte) 1)); // Empty string to prevent kick on 1.7
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
|
||||
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
|
||||
int color = 0;
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
|
||||
}
|
||||
player.sendPacket(new Team(name, (byte) 0, jsonOrRaw(name, player.getVersion()),
|
||||
jsonOrRaw(prefix, player.getVersion()), jsonOrRaw(suffix, player.getVersion()),
|
||||
visibility.toString(), collision.toString(), color, (byte)options, players.toArray(new String[0])));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
player.sendPacket(new Team(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
int color = 0;
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
|
||||
}
|
||||
player.sendPacket(new Team(name, (byte) 2, jsonOrRaw(name, player.getVersion()),
|
||||
jsonOrRaw(prefix, player.getVersion()), jsonOrRaw(suffix, player.getVersion()),
|
||||
visibility.toString(), collision.toString(), color, (byte)options, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
|
||||
* it as a serialized component, which BungeeCord uses.
|
||||
* <p>
|
||||
* If {@code clientVersion} is < 1.12, the text is returned
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @param clientVersion
|
||||
* Version of player to convert text for
|
||||
* @return serialized component for 1.13+ clients, cut string for 1.12-
|
||||
*/
|
||||
private String jsonOrRaw(@NotNull String text, @NotNull ProtocolVersion clientVersion) {
|
||||
if (clientVersion.getMinorVersion() >= 13) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
|
||||
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
|
||||
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
|
||||
/**
|
||||
* Main class for BungeeCord platform
|
||||
*/
|
||||
public class BungeeTAB extends Plugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
|
||||
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 and up (or an equivalent fork) to work."));
|
||||
return;
|
||||
}
|
||||
getProxy().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
|
||||
getProxy().getPluginManager().registerListener(this, new BungeeEventListener());
|
||||
getProxy().getPluginManager().registerCommand(this, new BungeeTabCommand());
|
||||
TAB.setInstance(new TAB(new BungeePlatform(this), ProtocolVersion.PROXY, getDataFolder()));
|
||||
TAB.getInstance().load();
|
||||
Metrics metrics = new Metrics(this, 10535);
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.TabExecutor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class BungeeTabCommand extends Command implements TabExecutor {
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BungeeTabCommand() {
|
||||
super(TabConstants.COMMAND_PROXY, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
|
||||
sender.sendMessage(new TextComponent(EnumChatFormat.color(message)));
|
||||
}
|
||||
} else {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof ProxiedPlayer) {
|
||||
p = TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId());
|
||||
if (p == null) return; //player not loaded correctly
|
||||
}
|
||||
TAB.getInstance().getCommand().execute(p, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
|
||||
TabPlayer p = null;
|
||||
if (sender instanceof ProxiedPlayer) {
|
||||
p = TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId());
|
||||
if (p == null) return new ArrayList<>(); //player not loaded correctly
|
||||
}
|
||||
return TAB.getInstance().getCommand().complete(p, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1193;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList17;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList18;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.Protocol;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for BungeeCord
|
||||
*/
|
||||
public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
/** Inaccessible bungee internals */
|
||||
private static @Nullable Object directionData;
|
||||
private static @Nullable Method getId;
|
||||
|
||||
/** Flag tracking plugin presence */
|
||||
private static final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
|
||||
|
||||
static {
|
||||
try {
|
||||
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
|
||||
getId = ReflectionUtils.setAccessible(directionData.getClass().getDeclaredMethod("getId", Class.class, int.class));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Player's scoreboard */
|
||||
@Getter private final @NotNull Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
|
||||
|
||||
/** Player's tab list based on version */
|
||||
private final @NotNull TabList tabList1_7 = new BungeeTabList17(this);
|
||||
private final @NotNull TabList tabList1_8 = new BungeeTabList18(this);
|
||||
private final @NotNull TabList tabList1_19_3 = new BungeeTabList1193(this);
|
||||
|
||||
@Getter private final @NotNull BossBar bossBar = new BungeeBossBar(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance for given player
|
||||
*
|
||||
* @param p
|
||||
* BungeeCord player
|
||||
*/
|
||||
public BungeeTabPlayer(@NotNull ProxiedPlayer p) {
|
||||
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission0(@NotNull String permission) {
|
||||
return getPlayer().hasPermission(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
return getPlayer().getPing();
|
||||
}
|
||||
|
||||
public void sendPacket(@NotNull Object nmsPacket) {
|
||||
getPlayer().unsafe().sendPacket((DefinedPacket) nmsPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
getPlayer().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TabList.Skin getSkin() {
|
||||
LoginResult loginResult = ((InitialHandler)getPlayer().getPendingConnection()).getLoginProfile();
|
||||
if (loginResult == null) return null;
|
||||
Property[] properties = loginResult.getProperties();
|
||||
if (properties == null || properties.length == 0) return null;
|
||||
return new TabList.Skin(properties[0].getValue(), properties[0].getSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ProxiedPlayer getPlayer() {
|
||||
return (ProxiedPlayer) player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns packet ID for this player of provided packet class
|
||||
*
|
||||
* @param clazz
|
||||
* packet class
|
||||
* @return packet ID
|
||||
*/
|
||||
public int getPacketId(@NotNull Class<? extends DefinedPacket> clazz) {
|
||||
if (getId == null) return -1;
|
||||
try {
|
||||
return (int) getId.invoke(directionData, clazz, getPlayer().getPendingConnection().getVersion());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
|
||||
" with client version " + getPlayer().getPendingConnection().getVersion(), e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If ViaVersion is installed on BungeeCord, it changes protocol to match version
|
||||
* of server to which player is connected to. For that reason, we need to retrieve
|
||||
* the field more often than just on join.
|
||||
*
|
||||
* @return Player's current protocol version
|
||||
*/
|
||||
@Override
|
||||
public @NotNull ProtocolVersion getVersion() {
|
||||
return ProtocolVersion.fromNetworkId(getPlayer().getPendingConnection().getVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
//noinspection ConstantConditions
|
||||
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
|
||||
return super.isVanished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer().isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TabList getTabList() {
|
||||
return getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() ?
|
||||
tabList1_19_3 : getVersion().getMinorVersion() >= 8 ? tabList1_8 : tabList1_7;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginMessage(byte[] message) {
|
||||
if (getPlayer().getServer() == null) {
|
||||
TAB.getInstance().getErrorManager().printError("Skipped plugin message send to " + getName() + ", because player is not" +
|
||||
"connected to any server (message=" + new String(message) + ")");
|
||||
return;
|
||||
}
|
||||
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Redis implementation for BungeeCord
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class RedisBungeeSupport extends RedisSupport implements Listener {
|
||||
|
||||
/** Plugin reference for registering listener */
|
||||
private final @NotNull Plugin plugin;
|
||||
|
||||
@EventHandler
|
||||
public void onMessage(@NotNull PubSubMessageEvent e) {
|
||||
if (!e.getChannel().equals(TabConstants.REDIS_CHANNEL_NAME)) return;
|
||||
processMessage(e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register() {
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(plugin, this);
|
||||
RedisBungeeAPI.getRedisBungeeApi().registerPubSubChannels(TabConstants.REDIS_CHANNEL_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
ProxyServer.getInstance().getPluginManager().unregisterListener(this);
|
||||
RedisBungeeAPI.getRedisBungeeApi().unregisterPubSubChannels(TabConstants.REDIS_CHANNEL_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull String message) {
|
||||
RedisBungeeAPI.getRedisBungeeApi().sendChannelMessage(TabConstants.REDIS_CHANNEL_NAME, message);
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItemRemove;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.19.3+ players using the new tab list packets.
|
||||
* Because BungeeCord does not have a TabList API, we need to use packets.
|
||||
* They are sent using an internal BungeeCord method that keeps track of them,
|
||||
* so they are removed on server switch to secure parity with Velocity.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeTabList1193 implements TabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
private final BungeeTabPlayer player;
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
PlayerListItemRemove remove = new PlayerListItemRemove();
|
||||
remove.setUuids(new UUID[]{entry});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(remove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
Item item = item(entry);
|
||||
item.setPing(latency);
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY), item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
Item item = item(entry);
|
||||
item.setGamemode(gameMode);
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE), item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
Item item = item(entry.getUniqueId());
|
||||
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
|
||||
item.setGamemode(entry.getGameMode());
|
||||
item.setListed(true);
|
||||
item.setPing(entry.getLatency());
|
||||
if (entry.getSkin() != null) {
|
||||
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
|
||||
} else {
|
||||
item.setProperties(new Property[0]);
|
||||
}
|
||||
item.setUsername(entry.getName());
|
||||
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), item);
|
||||
}
|
||||
|
||||
public @NotNull Item item(@NotNull UUID id) {
|
||||
Item item = new Item();
|
||||
item.setUuid(id);
|
||||
return item;
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull EnumSet<PlayerListItemUpdate.Action> actions, @NotNull Item item) {
|
||||
PlayerListItemUpdate packet = new PlayerListItemUpdate();
|
||||
packet.setActions(actions);
|
||||
packet.setItems(new Item[]{item});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.getPlayer().setTabHeader(header.toBungeeComponent(player.getVersion()), footer.toBungeeComponent(player.getVersion()));
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.7 players, which has a completely different
|
||||
* system. Entries are identified by display name, not uuids. Display
|
||||
* names are changed by removing old entry and adding a new one, which
|
||||
* requires further tracking than just the UUID.<p>
|
||||
* Because BungeeCord does not have a TabList API, we need to use packets.
|
||||
* They are sent using an internal BungeeCord method that keeps track of them,
|
||||
* so they are removed on server switch to secure parity with Velocity.
|
||||
* While BungeeCord itself does not support 1.7, some of its forks do.
|
||||
* This was tested on FlameCord fork.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeTabList17 implements TabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
private final BungeeTabPlayer player;
|
||||
|
||||
/** Because entries are identified by names and not uuids on 1.7 */
|
||||
private final Map<UUID, String> userNames = new HashMap<>();
|
||||
private final Map<UUID, String> displayNames = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
|
||||
|
||||
// Remove from map
|
||||
userNames.remove(entry);
|
||||
displayNames.remove(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
|
||||
addEntry(new Entry.Builder(entry).displayName(displayName).name(userNames.get(entry)).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.UPDATE_LATENCY, createItem(null, displayNames.get(entry), latency));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {/*Added in 1.8*/}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
String displayNameString = entry.getDisplayName() == null ? String.valueOf(entry.getName()) : entry.getDisplayName().toLegacyText();
|
||||
if (displayNameString.length() > 16) displayNameString = displayNameString.substring(0, 16); // 16 character limit
|
||||
update(PlayerListItem.Action.ADD_PLAYER, createItem(entry.getName(), displayNameString, entry.getLatency()));
|
||||
|
||||
// Add to map
|
||||
userNames.put(entry.getUniqueId(), entry.getName());
|
||||
displayNames.put(entry.getUniqueId(), displayNameString);
|
||||
}
|
||||
|
||||
private void update(@NotNull PlayerListItem.Action action, @NotNull PlayerListItem.Item item) {
|
||||
PlayerListItem packet = new PlayerListItem();
|
||||
packet.setAction(action);
|
||||
packet.setItems(new PlayerListItem.Item[]{item});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
|
||||
}
|
||||
|
||||
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
|
||||
PlayerListItem.Item item = new PlayerListItem.Item();
|
||||
item.setUsername(username);
|
||||
item.setDisplayName(displayName);
|
||||
item.setPing(latency);
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
// Added in 1.8
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler for players between 1.8 and 1.19.2.<p>
|
||||
* Because BungeeCord does not have a TabList API, we need to use packets.
|
||||
* They are sent using an internal BungeeCord method that keeps track of them,
|
||||
* so they are removed on server switch to secure parity with Velocity.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BungeeTabList18 implements TabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
private final BungeeTabPlayer player;
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
sendPacket(PlayerListItem.Action.REMOVE_PLAYER, item(entry));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
|
||||
sendPacket(PlayerListItem.Action.UPDATE_DISPLAY_NAME, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
Item item = item(entry);
|
||||
item.setPing(latency);
|
||||
sendPacket(PlayerListItem.Action.UPDATE_LATENCY, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
Item item = item(entry);
|
||||
item.setGamemode(gameMode);
|
||||
sendPacket(PlayerListItem.Action.UPDATE_GAMEMODE, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
Item item = item(entry.getUniqueId());
|
||||
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
|
||||
item.setGamemode(entry.getGameMode());
|
||||
item.setPing(entry.getLatency());
|
||||
if (entry.getSkin() != null) {
|
||||
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
|
||||
} else {
|
||||
item.setProperties(new Property[0]);
|
||||
}
|
||||
item.setUsername(entry.getName());
|
||||
sendPacket(PlayerListItem.Action.ADD_PLAYER, item);
|
||||
}
|
||||
|
||||
public @NotNull Item item(@NotNull UUID id) {
|
||||
Item item = new Item();
|
||||
item.setUuid(id);
|
||||
return item;
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull PlayerListItem.Action action, @NotNull Item item) {
|
||||
PlayerListItem packet = new PlayerListItem();
|
||||
packet.setAction(action);
|
||||
packet.setItems(new Item[]{item});
|
||||
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.getPlayer().setTabHeader(header.toBungeeComponent(player.getVersion()), footer.toBungeeComponent(player.getVersion()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
name: @name@
|
||||
main: me.neznamy.tab.platforms.bungeecord.BungeeTAB
|
||||
version: @version@
|
||||
author: @author@
|
||||
description: @description@
|
||||
depends: []
|
||||
softDepends: [RedisBungee, LuckPerms, PremiumVanish]
|
||||
@@ -0,0 +1,78 @@
|
||||
// Versions that don't require any code changes:
|
||||
// 1.19.3, 1.19.4, 1.20, 1.20.1
|
||||
val minecraftVersion = "1.20.1"
|
||||
|
||||
val v114 = "0.28.5+1.14"
|
||||
val v115 = "0.28.5+1.15"
|
||||
val v116 = "0.42.0+1.16"
|
||||
|
||||
val fabricApiVersions = mapOf(
|
||||
"1.20.1" to "0.83.0+1.20.1",
|
||||
"1.20" to "0.83.0+1.20",
|
||||
"1.19.4" to "0.77.0+1.19.4",
|
||||
"1.19.3" to "0.76.0+1.19.3",
|
||||
"1.19.2" to "0.76.0+1.19.2",
|
||||
"1.19.1" to "0.58.5+1.19.1",
|
||||
"1.19" to "0.58.0+1.19",
|
||||
"1.18.2" to "0.76.0+1.18.2",
|
||||
"1.18.1" to "0.46.6+1.18",
|
||||
"1.18" to "0.46.6+1.18",
|
||||
"1.17.1" to "0.46.1+1.17",
|
||||
"1.17" to "0.46.1+1.17",
|
||||
"1.16.5" to v116,
|
||||
"1.16.4" to v116,
|
||||
"1.16.3" to v116,
|
||||
"1.16.2" to v116,
|
||||
"1.16.1" to v116,
|
||||
"1.16" to v116,
|
||||
"1.15.2" to v115,
|
||||
"1.15.1" to v115,
|
||||
"1.15" to v115,
|
||||
"1.14.4" to v114,
|
||||
"1.14.3" to v114,
|
||||
"1.14.2" to v114,
|
||||
"1.14.1" to v114,
|
||||
"1.14" to v114,
|
||||
)
|
||||
|
||||
plugins {
|
||||
id("fabric-loom")
|
||||
}
|
||||
|
||||
repositories {
|
||||
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
|
||||
maven("https://repo.kryptonmc.org/releases")
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
|
||||
minecraft("com.mojang:minecraft:$minecraftVersion")
|
||||
mappings(loom.officialMojangMappings())
|
||||
|
||||
modImplementation("net.fabricmc", "fabric-loader", "0.14.17")
|
||||
modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT")
|
||||
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersions[minecraftVersion]))
|
||||
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersions[minecraftVersion]))
|
||||
modImplementation(fabricApi.module("fabric-networking-api-v1", fabricApiVersions[minecraftVersion]))
|
||||
modImplementation(fabricApi.module("fabric-entity-events-v1", fabricApiVersions[minecraftVersion]))
|
||||
modImplementation(fabricApi.module("fabric-command-api-v${if (minecraftVersion.split(".")[1].toInt() >= 19) "2" else "1"}", fabricApiVersions[minecraftVersion]))
|
||||
}
|
||||
|
||||
loom {
|
||||
accessWidenerPath.set(file("src/main/resources/tab.accesswidener"))
|
||||
}
|
||||
|
||||
tasks {
|
||||
compileJava {
|
||||
options.release.set(17)
|
||||
}
|
||||
validateAccessWidener {
|
||||
// We don't want to validate the access wideners, as we have wideners for multiple versions, and the validation task will
|
||||
// fail, as the classes for the old wideners don't exist in the new versions and vice versa.
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.bossbar.BossBar;
|
||||
import net.minecraft.server.level.ServerBossEvent;
|
||||
import net.minecraft.world.BossEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class FabricBossBar implements BossBar {
|
||||
|
||||
private final FabricTabPlayer player;
|
||||
private final Map<UUID, ServerBossEvent> bars = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
ServerBossEvent bar = new ServerBossEvent(
|
||||
FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
|
||||
BossEvent.BossBarColor.valueOf(color.name()),
|
||||
BossEvent.BossBarOverlay.valueOf(style.name())
|
||||
);
|
||||
bar.setProgress(progress);
|
||||
bars.put(id, bar);
|
||||
bar.addPlayer(player.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
bars.get(id).setName(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
bars.get(id).setProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarStyle style) {
|
||||
bars.get(id).setOverlay(BossEvent.BossBarOverlay.valueOf(style.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull BarColor color) {
|
||||
bars.get(id).setColor(BossEvent.BossBarColor.valueOf(color.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull UUID id) {
|
||||
bars.remove(id).removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
public class FabricEventListener extends EventListener<ServerPlayer> {
|
||||
|
||||
public void register() {
|
||||
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
|
||||
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
|
||||
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> replacePlayer(oldPlayer.getUUID(), newPlayer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabPlayer createPlayer(ServerPlayer player) {
|
||||
return new FabricTabPlayer(player);
|
||||
}
|
||||
|
||||
//TODO world switch
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import me.neznamy.tab.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FabricPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
|
||||
@Override
|
||||
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((ServerPlayer) p.getPlayer()).getHealth()));
|
||||
super.registerPlaceholders(manager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class FabricPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (ServerPlayer player : PlayerLookup.all(FabricTAB.getInstance().getServer())) {
|
||||
TAB.getInstance().addPlayer(new FabricTabPlayer(player));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerPlaceholders() {
|
||||
new FabricPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull NameTag getUnlimitedNameTags() {
|
||||
return new NameTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PipelineInjector createPipelineInjector() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TabExpansion createTabExpansion() {
|
||||
return new EmptyTabExpansion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TabFeature getPerWorldPlayerList() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
|
||||
MinecraftServer.LOGGER.info("[TAB] " + message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerVersionInfo() {
|
||||
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
|
||||
}
|
||||
}
|
||||
Loaded 100 of 557 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user