Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17f01fb067 | ||
|
|
52f3c6a998 | ||
|
|
45766c5265 | ||
|
|
4d8f6b5f50 | ||
|
|
8acf512c80 | ||
|
|
deff13deb6 | ||
|
|
39a034d92c | ||
|
|
80b196cac0 | ||
|
|
42576f10ce | ||
|
|
328e031c1b | ||
|
|
0bc781f390 | ||
|
|
ab6920c300 | ||
|
|
c7f4bd15d2 | ||
|
|
9af7a7267e | ||
|
|
3ff9e774cd | ||
|
|
36e5eb9304 | ||
|
|
b7c9deef83 | ||
|
|
0d2940c34a | ||
|
|
a793912337 | ||
|
|
d1194223ab | ||
|
|
0624b117b5 | ||
|
|
7fed456871 | ||
|
|
5f4ec6a1a7 | ||
|
|
3c400f1eeb | ||
|
|
f6dba5ee8e | ||
|
|
8b3cbab3ad | ||
|
|
c0e6ae7ec6 | ||
|
|
6a5772b26b | ||
|
|
71d6d9e33f | ||
|
|
0508bd98ba | ||
|
|
5b9efc0ea5 | ||
|
|
a3812b2546 | ||
|
|
05fb233f0e | ||
|
|
e15277d58d | ||
|
|
177ae4cbfa | ||
|
|
c1db1479b6 | ||
|
|
86f89c010d | ||
|
|
5de92f28de | ||
|
|
96ab0c3bdf | ||
|
|
a4a1397b87 | ||
|
|
4b5cf2a9a1 | ||
|
|
fa86c87693 | ||
|
|
6b639048b9 | ||
|
|
5413484c2a | ||
|
|
6d6b27dfe2 | ||
|
|
fde5efa31f | ||
|
|
cccc0194d6 | ||
|
|
d8e7bc8f3e | ||
|
|
dd37d3407b | ||
|
|
c0c19d1a2d | ||
|
|
59eb669c17 | ||
|
|
86cb4af960 | ||
|
|
056b9f0486 | ||
|
|
afe483c0ad | ||
|
|
5d4c8f6f85 | ||
|
|
a08963e1dc | ||
|
|
50a2b3af95 | ||
|
|
01cd47a6b6 | ||
|
|
7494b995bc | ||
|
|
e4ef877188 | ||
|
|
efa8b750e7 | ||
|
|
21d21d2509 | ||
|
|
44011acca2 | ||
|
|
71791a1020 | ||
|
|
665a43bd4c | ||
|
|
5ce81e0fe4 | ||
|
|
29b6e12cb8 | ||
|
|
014c5f7670 | ||
|
|
9c9316e224 | ||
|
|
9e296cbf5a | ||
|
|
0af47a1266 | ||
|
|
8e6219c597 | ||
|
|
9aa555ff78 | ||
|
|
21c0833769 | ||
|
|
450642d888 | ||
|
|
4fa3ef68e2 | ||
|
|
f477ba4c62 | ||
|
|
4ac58782ac | ||
|
|
2f6242027b | ||
|
|
d8c0aef8cc | ||
|
|
3f1b4d8472 | ||
|
|
0adc332d14 | ||
|
|
cda6af1f34 | ||
|
|
237f3744d4 | ||
|
|
99b309a0b3 | ||
|
|
ad68a43b83 | ||
|
|
e7abaf8523 | ||
|
|
a508002c48 | ||
|
|
259696710f | ||
|
|
a47a34e9f9 | ||
|
|
4eb5ad4e87 | ||
|
|
46e616107e | ||
|
|
dcb72a9efd | ||
|
|
b939ba5921 | ||
|
|
786611aeec | ||
|
|
44eba3b446 | ||
|
|
0f11cb74b8 | ||
|
|
1cff3e526f | ||
|
|
a09093ee5b | ||
|
|
c1db9edd7d | ||
|
|
9ac2b4fbb6 | ||
|
|
0b9ba69afc | ||
|
|
b326c7b961 | ||
|
|
f1243073f2 | ||
|
|
4e4f23479a | ||
|
|
c7a4eeec94 | ||
|
|
d1a2dbf6aa | ||
|
|
c67258ac13 | ||
|
|
ca7da355f7 | ||
|
|
2a452c7d85 | ||
|
|
66776e8163 | ||
|
|
3089036d43 | ||
|
|
447ce3466d | ||
|
|
244113714e | ||
|
|
ae1c47d21f | ||
|
|
241525c2c2 | ||
|
|
d2b529aceb | ||
|
|
89f0aefe22 | ||
|
|
8d8499d6fc | ||
|
|
3bd1673e67 | ||
|
|
2ee47a23ea | ||
|
|
734d227175 | ||
|
|
1377747bec | ||
|
|
210a9266a4 | ||
|
|
616c46d97e | ||
|
|
aa69b6f69f | ||
|
|
fae66e9039 | ||
|
|
fb05fcb1fa | ||
|
|
acdcfe8f71 | ||
|
|
e3f5db6f19 | ||
|
|
be48c35eda | ||
|
|
2ea1d29da5 | ||
|
|
c49e531853 | ||
|
|
2eebcfd056 | ||
|
|
f1988e66be | ||
|
|
d7474db60d | ||
|
|
97b3781f49 | ||
|
|
a99e0e4947 | ||
|
|
ec88893d52 | ||
|
|
c1670a7f4e | ||
|
|
3e185b6e1e | ||
|
|
d714211fe6 | ||
|
|
71d524aad0 | ||
|
|
9bf8eed3d1 | ||
|
|
74c8c5ff01 | ||
|
|
935b26ca0c | ||
|
|
5e2f889550 | ||
|
|
b003ec799b | ||
|
|
11037cdc85 | ||
|
|
fffae61f8a | ||
|
|
6a56b9bbf0 | ||
|
|
4c13a0adec | ||
|
|
0932b7062d | ||
|
|
7f61d19f55 | ||
|
|
31396cdd73 | ||
|
|
ceb6e7932b | ||
|
|
82a755d739 | ||
|
|
631217a7f4 | ||
|
|
b880f51fbc | ||
|
|
2a86ff4e5f | ||
|
|
3d3a17eea9 | ||
|
|
ac2607d6cd | ||
|
|
b4820b5f19 | ||
|
|
79c70572a8 | ||
|
|
ced56218d9 | ||
|
|
6243fdf605 | ||
|
|
b40deaa620 | ||
|
|
571ae73fc5 | ||
|
|
8d2d33f1ea | ||
|
|
738cd44ab9 | ||
|
|
b800699cbf | ||
|
|
d3c82a326a | ||
|
|
e40ba62e5a | ||
|
|
bcc9e9be13 | ||
|
|
40918c9988 | ||
|
|
f493fe3005 | ||
|
|
c5dc6d4050 | ||
|
|
db8aad3c00 | ||
|
|
12cdd2f380 | ||
|
|
6c3657ae40 | ||
|
|
ffa21f6f3e | ||
|
|
7ce0e2f9ec | ||
|
|
1c3c6a8879 | ||
|
|
75aa7d8f71 | ||
|
|
33c96893d7 | ||
|
|
c3f6fd5c65 | ||
|
|
616f3076c9 | ||
|
|
0898a94104 | ||
|
|
48a371dc66 | ||
|
|
5e7fef1572 | ||
|
|
8701f12f6a | ||
|
|
cbdd5a7a14 | ||
|
|
48ba546a40 | ||
|
|
cdbe983d22 | ||
|
|
e2ad24e604 | ||
|
|
d954784e74 | ||
|
|
dd0e338588 | ||
|
|
73d591907d | ||
|
|
806a13ea25 | ||
|
|
38d64e2cff | ||
|
|
c666d98a9e | ||
|
|
c270105c09 | ||
|
|
36776a488b | ||
|
|
5d6c77851a | ||
|
|
8bb5be493d | ||
|
|
c681a12176 | ||
|
|
ea614f7336 | ||
|
|
fa985fe4fa | ||
|
|
f485b70cbf | ||
|
|
36e0974fcf | ||
|
|
a4ee272641 | ||
|
|
fd8507610b | ||
|
|
4428af4acc | ||
|
|
a4031c4862 | ||
|
|
7abb8d61b0 | ||
|
|
0b76452fbb | ||
|
|
0c8b4c2f3a | ||
|
|
42af76a9ea | ||
|
|
7a0f2b1227 | ||
|
|
1789912e14 | ||
|
|
6cbedf1f26 | ||
|
|
2c42a96c3c | ||
|
|
bc1ab00c3f | ||
|
|
34fde514fd | ||
|
|
26d5d9d9d2 | ||
|
|
f755f2d670 | ||
|
|
3004390528 | ||
|
|
af141e35d3 | ||
|
|
3a33be168f | ||
|
|
30994d1279 | ||
|
|
9b716b5c58 | ||
|
|
714549f79c | ||
|
|
d0fcf33255 | ||
|
|
02553f332f | ||
|
|
1eb12bafd4 | ||
|
|
88883caca8 | ||
|
|
5a8e8b320a | ||
|
|
3723581dc2 | ||
|
|
7ea0d4d24c | ||
|
|
dff5361934 | ||
|
|
cd032a51ab | ||
|
|
379fc6601d | ||
|
|
dd490aa8c2 | ||
|
|
42094cbddc | ||
|
|
d0562de1e1 | ||
|
|
d85979d3ab | ||
|
|
3809aed146 | ||
|
|
06210f03ba | ||
|
|
97afbd8d77 | ||
|
|
df89d15aa6 | ||
|
|
12864dccc5 | ||
|
|
4c9b07612d | ||
|
|
026c2417c9 | ||
|
|
9621add16e | ||
|
|
7bd3af3eca | ||
|
|
dd9d14e195 | ||
|
|
8d01e90f11 | ||
|
|
6ed64d3730 | ||
|
|
8660ff0f60 | ||
|
|
fd889bd26a | ||
|
|
1a7949a477 | ||
|
|
0a26d37728 | ||
|
|
73597d9524 | ||
|
|
1799a56072 | ||
|
|
56e4fa75bf | ||
|
|
b535b28661 | ||
|
|
43634772c7 | ||
|
|
aea66feb33 | ||
|
|
11d856ce08 | ||
|
|
30a346bd3f | ||
|
|
5254a376c4 | ||
|
|
f79e8c12a5 | ||
|
|
af580c4d48 | ||
|
|
be65dc5e51 | ||
|
|
93c1c7b480 | ||
|
|
b0a5dbd07f | ||
|
|
74cca86bb6 | ||
|
|
fe4e60c80c | ||
|
|
22dd134196 | ||
|
|
e971604565 | ||
|
|
90c59ad4df | ||
|
|
c888256b38 | ||
|
|
72a0ad53dd | ||
|
|
2fc13af09d | ||
|
|
57ccbe753e | ||
|
|
d14916c590 | ||
|
|
5b8741ca46 | ||
|
|
cfcf52f337 | ||
|
|
ab32f24dc1 | ||
|
|
a653e17f3c | ||
|
|
809d26307f | ||
|
|
d35d326595 | ||
|
|
81c550e834 | ||
|
|
b27cea2385 | ||
|
|
79f1d65713 | ||
|
|
737e6c4cfb | ||
|
|
44f68a3266 | ||
|
|
4e69adc548 | ||
|
|
93d0c38393 | ||
|
|
81aa8b8d31 | ||
|
|
ce28bb689a | ||
|
|
487ce68ef6 | ||
|
|
a29f7a2807 | ||
|
|
80d33f0f11 | ||
|
|
0bd57102d5 | ||
|
|
c701b22098 | ||
|
|
0ad3c58496 | ||
|
|
99e06d6b3b | ||
|
|
4785482543 | ||
|
|
c06beba53b | ||
|
|
4b989a15b6 | ||
|
|
0f10d48519 | ||
|
|
2e7454de84 | ||
|
|
5faad6b3ba | ||
|
|
75b0f5c344 | ||
|
|
d0b94bbda5 | ||
|
|
6f18622643 | ||
|
|
6b863655f1 | ||
|
|
631be06ec2 | ||
|
|
1c4361f2c6 | ||
|
|
2b5016442b | ||
|
|
e389e4b5e1 | ||
|
|
7d9cdb91b0 | ||
|
|
83e06bce77 | ||
|
|
42fd3e1cc7 | ||
|
|
26a771e4da | ||
|
|
fad1eb5736 | ||
|
|
1dc4361938 | ||
|
|
3d5491de4f | ||
|
|
75ff92c326 | ||
|
|
c530dcfa17 | ||
|
|
d720b8864f | ||
|
|
3e3dbf0f30 | ||
|
|
0bd154974b | ||
|
|
3f57026960 | ||
|
|
be9841fb9f | ||
|
|
d8b529e837 | ||
|
|
d01805ad13 | ||
|
|
f638772c5f | ||
|
|
b84b21a68f | ||
|
|
da230781e3 | ||
|
|
c91ef45062 | ||
|
|
9a87329618 | ||
|
|
24ee315c0b | ||
|
|
a4decb57f8 | ||
|
|
81feb5be3f | ||
|
|
2253fd0ec3 | ||
|
|
db4ad8e16d | ||
|
|
11571a2b8a | ||
|
|
c615c98d09 | ||
|
|
922b134467 | ||
|
|
6650e0cece | ||
|
|
8714e0e5a9 | ||
|
|
24570aa658 | ||
|
|
743a2d9cdb | ||
|
|
4b28aa7604 | ||
|
|
50425ada16 | ||
|
|
6d4f3650cc | ||
|
|
de7a5358d2 | ||
|
|
9a0f3a4dd1 | ||
|
|
bfbde9d8fc | ||
|
|
a5ced59a35 | ||
|
|
e9c6fac17a | ||
|
|
2bc467ac82 | ||
|
|
c68f7bd5f8 | ||
|
|
7015a9efa2 | ||
|
|
06bd96fcae | ||
|
|
db7bbef9b5 | ||
|
|
fa0d147e04 | ||
|
|
39e7cbe9ca | ||
|
|
84a1641968 | ||
|
|
26acd7b3a5 | ||
|
|
f51e5e4eae | ||
|
|
636eaf3d5d | ||
|
|
5b9fdd00c8 | ||
|
|
bc42035866 | ||
|
|
917eb371c8 | ||
|
|
ef45df9ea3 | ||
|
|
24965108b2 | ||
|
|
9f09fcd103 | ||
|
|
e1baecd32e | ||
|
|
44d66e0e5b | ||
|
|
0368f930b8 | ||
|
|
a83a0c8579 | ||
|
|
92b1ad28c2 | ||
|
|
5302f15b9d | ||
|
|
6a3eec1960 | ||
|
|
11425840ad | ||
|
|
8515df8224 | ||
|
|
fe5d6e34f2 | ||
|
|
e3e797e19a | ||
|
|
2293092048 | ||
|
|
147081ccc5 | ||
|
|
b924f65f91 | ||
|
|
259dd72f87 | ||
|
|
ab726bda78 | ||
|
|
163bbd7b0e | ||
|
|
205e169754 | ||
|
|
35f7914249 | ||
|
|
31b7fd462f | ||
|
|
b9812ba2b6 | ||
|
|
fc1d5ff3ae | ||
|
|
2f983ad316 | ||
|
|
db23910195 | ||
|
|
968e166bde | ||
|
|
efe729c4d1 | ||
|
|
d40d19d6e3 | ||
|
|
b476b143f4 | ||
|
|
bb54697d9c | ||
|
|
24102c15e6 | ||
|
|
5100ef06c8 | ||
|
|
9b892eab85 | ||
|
|
eab5ca7f6d | ||
|
|
fd055c163e | ||
|
|
ea78d2ff40 | ||
|
|
6f074513f2 | ||
|
|
0719c5bdc6 | ||
|
|
36a394d142 | ||
|
|
0e08dbc6f2 | ||
|
|
003aa67a41 | ||
|
|
be45a43524 | ||
|
|
0d700848f8 | ||
|
|
1ff402036d | ||
|
|
9d36dbd2b8 | ||
|
|
c53c4378d8 | ||
|
|
eacf176f88 | ||
|
|
13afb067bb | ||
|
|
7af2f4b2e2 | ||
|
|
a1d8bfa6a1 | ||
|
|
47b3fe9ca2 | ||
|
|
0ce52ea107 | ||
|
|
d98fe31412 | ||
|
|
d8f338aada | ||
|
|
f5965c48b3 | ||
|
|
0df7077ddb | ||
|
|
222a9341bf | ||
|
|
35f6f7870a | ||
|
|
15b6e0ba27 | ||
|
|
968754efe0 | ||
|
|
a03c9e0194 | ||
|
|
734e7cbae8 | ||
|
|
52fe6691ac | ||
|
|
acd8d1192c | ||
|
|
8fc309cb76 | ||
|
|
45f16ec10f | ||
|
|
5ff8cb21c8 | ||
|
|
f8997b8739 | ||
|
|
ed21464c3a | ||
|
|
7954575216 | ||
|
|
1a11d3e733 | ||
|
|
487aa45a27 | ||
|
|
b7f9aeab06 | ||
|
|
6aaee382a6 | ||
|
|
8c84c506bf | ||
|
|
0622f92cf1 | ||
|
|
a8f81c141a | ||
|
|
efffceb9f2 | ||
|
|
75d1fc32c8 | ||
|
|
e087e93e2a | ||
|
|
e83c9967b4 | ||
|
|
2b1896020d | ||
|
|
61b17907ee | ||
|
|
339fb42f37 | ||
|
|
7985275697 | ||
|
|
661b60d07c | ||
|
|
d4f647e18b | ||
|
|
1761d853de | ||
|
|
42a6543f9b | ||
|
|
9356c6c323 | ||
|
|
a7a92f7e10 | ||
|
|
e149ca6be8 | ||
|
|
d44493743d | ||
|
|
7eb030de7d | ||
|
|
8fd4a4a87d | ||
|
|
8f71a427fd | ||
|
|
8b9bc356f2 | ||
|
|
89b6c8306e | ||
|
|
32a09aa36b | ||
|
|
3ea716c580 | ||
|
|
328d347845 | ||
|
|
d394c3d67c | ||
|
|
17b6b8a4e7 | ||
|
|
980226fab9 | ||
|
|
0f726a0f34 | ||
|
|
91ee057f4a | ||
|
|
c26afbc0af | ||
|
|
be56e28009 | ||
|
|
0697c425ca | ||
|
|
ca0ca471fb | ||
|
|
7b2aac3874 | ||
|
|
fbe8376ca0 | ||
|
|
22bc18859b | ||
|
|
25f9608d58 | ||
|
|
428f213e15 | ||
|
|
593d48a6e6 | ||
|
|
3dcf3c931a | ||
|
|
8cc45ddba3 | ||
|
|
6f23122657 | ||
|
|
c0b0759ed0 | ||
|
|
00252f3b97 | ||
|
|
531c49011b | ||
|
|
dea22454f7 | ||
|
|
12bda63785 | ||
|
|
c65e17cf0c | ||
|
|
753ca0e6ad | ||
|
|
8b85e1711d | ||
|
|
12b0dfd19d | ||
|
|
8c05d41423 | ||
|
|
c5ab0f5440 | ||
|
|
d92f5ef375 | ||
|
|
c313d6203a | ||
|
|
070f92aea2 | ||
|
|
4e3c5894b5 | ||
|
|
8cfb9f0f17 | ||
|
|
4e135e3f92 | ||
|
|
938522974b | ||
|
|
6994975387 | ||
|
|
6d3a172466 | ||
|
|
3aaa185617 | ||
|
|
980cd92333 | ||
|
|
89da2942c7 | ||
|
|
8ea905e053 | ||
|
|
885e959cdb | ||
|
|
02904a3dce | ||
|
|
e8d4bb2376 | ||
|
|
7695167578 | ||
|
|
94e5f63a65 | ||
|
|
9fa0f773d5 | ||
|
|
88111e6f7e | ||
|
|
9a5c16fe6a | ||
|
|
4efaf8d497 | ||
|
|
07881da451 | ||
|
|
bd6c2524e7 | ||
|
|
b096e02b9b | ||
|
|
ad6b24345b | ||
|
|
9694b756b5 | ||
|
|
6b5786be9d | ||
|
|
1f49ab2551 | ||
|
|
b18a5bec8e | ||
|
|
d741459e2f | ||
|
|
7007722aca | ||
|
|
05e7b3c1e5 | ||
|
|
13554546eb | ||
|
|
e2f3722b52 | ||
|
|
1b75734046 | ||
|
|
61d9609328 | ||
|
|
4cff51c89f | ||
|
|
ace5178e99 | ||
|
|
4a9d8627d3 | ||
|
|
fc89b932dd | ||
|
|
0852ac8326 | ||
|
|
87194d7f1b | ||
|
|
31fa689a98 | ||
|
|
5d366e742c | ||
|
|
7cc3c8f5ef | ||
|
|
7215dfcf60 | ||
|
|
5c709c1c54 | ||
|
|
1e5ad65c9f | ||
|
|
f6aeffb264 | ||
|
|
4593f3077c | ||
|
|
b92c8726de | ||
|
|
c560539a16 | ||
|
|
9cba7a95f7 | ||
|
|
d6d86ef04d | ||
|
|
ed643f3481 | ||
|
|
d8c2f77a0b | ||
|
|
6cfda647f7 | ||
|
|
fc58fcc721 | ||
|
|
5f0707956a | ||
|
|
528712e931 | ||
|
|
8fe609452b | ||
|
|
3afd2c4ac9 | ||
|
|
5793d7b678 | ||
|
|
569a8b4c4d | ||
|
|
9cab8f7469 | ||
|
|
1ac0b2efbe | ||
|
|
b78a36f1c2 | ||
|
|
e23d191cb8 | ||
|
|
8854b00b46 | ||
|
|
4be835de07 | ||
|
|
b8f59fa99c | ||
|
|
2236efea4f | ||
|
|
344ba4138d | ||
|
|
ba0fa7a869 | ||
|
|
21668a63fd | ||
|
|
2f6f9bcbc5 | ||
|
|
86d19de452 | ||
|
|
d4986449f1 | ||
|
|
3fc366bf1a | ||
|
|
9d192b215e | ||
|
|
b4ea4517e9 | ||
|
|
07ad12a16f | ||
|
|
f7529a18df | ||
|
|
c66522cb47 | ||
|
|
94a31f77ff | ||
|
|
c0630f9f6d | ||
|
|
3a145cb615 | ||
|
|
837516ea86 | ||
|
|
66228f59be | ||
|
|
800c79a898 | ||
|
|
2177b12bcf | ||
|
|
050c22ce27 | ||
|
|
ded614f30f | ||
|
|
88e439ebfd | ||
|
|
a027355c7d | ||
|
|
8a351ec928 | ||
|
|
b2fa4974ef | ||
|
|
9c2a034b43 | ||
|
|
45ba746300 | ||
|
|
483f317f83 | ||
|
|
dee3268bce | ||
|
|
2a88d3eef4 | ||
|
|
2898fed15a | ||
|
|
d2721077c9 | ||
|
|
dd7bb81b46 | ||
|
|
4515d21023 | ||
|
|
3315e3c148 | ||
|
|
2b552680a7 | ||
|
|
f78c80f04d | ||
|
|
bc239a7994 | ||
|
|
a508ec2279 | ||
|
|
cccecbacbc | ||
|
|
177a8b0527 | ||
|
|
d869ff959f | ||
|
|
772ba562e6 | ||
|
|
1786360d7c | ||
|
|
328596f01f | ||
|
|
6a13c9f822 | ||
|
|
3c78a63719 | ||
|
|
8b645b75df | ||
|
|
ae3ea30c92 | ||
|
|
01bd2c0c11 | ||
|
|
e91721299a | ||
|
|
4e08ec9fca | ||
|
|
b599008040 | ||
|
|
831f68b00c | ||
|
|
bccda6cb9e | ||
|
|
d0e77d2959 | ||
|
|
441ff72e1e | ||
|
|
aff131394e | ||
|
|
7a6f758853 | ||
|
|
eef8f538bc | ||
|
|
42d79ea01f | ||
|
|
df2c92555f | ||
|
|
380f6b5581 | ||
|
|
fb6450fb72 | ||
|
|
bb01c25605 | ||
|
|
d812e7dd8b | ||
|
|
60f3a765f3 | ||
|
|
31d6bd3664 | ||
|
|
4c0018b04c | ||
|
|
4b0b3df9d2 | ||
|
|
f699be1a65 | ||
|
|
b6496b31f0 | ||
|
|
1e601c3de9 | ||
|
|
00b39fec38 | ||
|
|
9224ab8a76 | ||
|
|
5307aebbf5 | ||
|
|
e7d8d613aa | ||
|
|
f469b08505 | ||
|
|
60b2a07659 | ||
|
|
8d6d448073 | ||
|
|
798156fedc | ||
|
|
e18510fc6b | ||
|
|
0fe96bf32d | ||
|
|
6d1f491b80 | ||
|
|
07ac8b99f0 | ||
|
|
622c35e935 | ||
|
|
c6adad5992 | ||
|
|
211722a8a7 | ||
|
|
a0082a4ce7 | ||
|
|
a4eb540d23 | ||
|
|
2d427d6c47 | ||
|
|
0d00168aaf |
No files matched your search
@@ -15,24 +15,98 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
contents: write # Allows the workflow to create or update references (tags)
|
||||
packages: write
|
||||
actions: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
arguments: build
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: /home/runner/work/Grim/Grim/build/libs/**.jar
|
||||
# Temporary tasks so built jar works because PacketEvents won't merge my fixes for their bugs yet
|
||||
- name: Clone Axionize's PacketEvents Repository
|
||||
run: |
|
||||
git clone -b fix/fabric-events https://github.com/Axionize/packetevents.git packetevents
|
||||
cd packetevents
|
||||
echo "Cloned PacketEvents repository"
|
||||
|
||||
- name: Build and Publish PacketEvents to Maven Local
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
build-root-directory: packetevents
|
||||
arguments: publishToMavenLocal
|
||||
|
||||
- name: Extract Version from build.gradle.kts
|
||||
id: extract-version
|
||||
run: |
|
||||
# Extract the version from build.gradle.kts using grep and sed
|
||||
VERSION=$(grep "^version\s*=" build.gradle.kts | sed 's/version\s*=\s*"\(.*\)"/\1/')
|
||||
echo "VERSION: $VERSION"
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build Lite Jar with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
arguments: build -PshadePE=false
|
||||
|
||||
- name: Upload Lite Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: grimac-lite
|
||||
path: ${{ github.workspace }}/build/libs/grimac-${{ env.VERSION }}-lite.jar
|
||||
|
||||
- name: Build Main Jar with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
arguments: build
|
||||
|
||||
- name: Upload Main Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: grimac-main
|
||||
path: ${{ github.workspace }}/build/libs/grimac-${{ env.VERSION }}.jar
|
||||
|
||||
- name: Generate build number
|
||||
if: contains(fromJSON('["merge", "release", "main"]'), github.ref_name)
|
||||
id: buildnumber
|
||||
uses: onyxmueller/build-tag-number@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish to Modrinth
|
||||
if: contains(fromJSON('["merge", "release", "main"]'), github.ref_name)
|
||||
uses: Kir-Antipov/mc-publish@v3.3
|
||||
with:
|
||||
modrinth-id: ${{ vars.MODRINTH_ID }}
|
||||
modrinth-featured: true
|
||||
modrinth-unfeature-mode: subset
|
||||
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
|
||||
|
||||
files: |
|
||||
${{ github.workspace }}/build/libs/grimac-${{ env.VERSION }}.jar
|
||||
${{ github.workspace }}/build/libs/grimac-${{ env.VERSION }}-lite.jar
|
||||
|
||||
name: Lightning Grim Anticheat ${{ env.VERSION }}-b${{ steps.buildnumber.outputs.build_number }}
|
||||
version: ${{ env.VERSION }}-b${{ steps.buildnumber.outputs.build_number }}
|
||||
version-type: release
|
||||
|
||||
loaders: |
|
||||
bukkit
|
||||
spigot
|
||||
paper
|
||||
folia
|
||||
purpur
|
||||
|
||||
game-versions: |
|
||||
>=1.7
|
||||
|
||||
retry-attempts: 2
|
||||
retry-delay: 10000
|
||||
fail-mode: fail
|
||||
@@ -11,8 +11,7 @@ This project is considered feature complete for the 2.0 (open-source) branch of
|
||||
- *For bleeding edge builds use* [Github artifacts](https://nightly.link/GrimAnticheat/Grim/workflows/gradle-publish/2.0/artifact.zip)
|
||||
|
||||
## Installation notes
|
||||
> [!WARNING]
|
||||
> Java 17 is now required. More information [here](https://github.com/GrimAnticheat/Grim/wiki/Updating-to-Java-17).
|
||||
- Java 8 is fully supported.
|
||||
- Paper, Spigot, and Folia are currently supported.
|
||||
- If you use Geyser, place Floodgate on the backend server so grim can exempt bedrock players. Grim cannot access the Floodgate API if it is on the proxy.
|
||||
- If you use ViaVersion, it should be on the backend server as movement is highly dependent on client version.
|
||||
|
||||
+37
-9
@@ -26,8 +26,10 @@ spotless {
|
||||
}
|
||||
|
||||
group = "ac.grim.grimac"
|
||||
version = "2.3.71"
|
||||
version = "2.3.72"
|
||||
description = "Libre simulation anticheat designed for 1.21 with 1.8-1.21 support, powered by PacketEvents 2.0."
|
||||
java.sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
java.targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
// Set to false for debug builds
|
||||
// You cannot live reload classes if the jar relocates dependencies
|
||||
@@ -36,7 +38,14 @@ val relocate: Boolean = project.findProperty("relocate")?.toString()?.toBoolean(
|
||||
?: System.getenv("RELOCATE_JAR")?.toBoolean()
|
||||
?: true
|
||||
|
||||
// Whether or not to shade PE into the jar, some servers may not want this if they use other plugins
|
||||
// With PE; this prevents duplicate listening and reduces RAM usage by ~80-100 MB
|
||||
val shadePE: Boolean = project.findProperty("shadePE")?.toString()?.toBoolean()
|
||||
?: System.getenv("SHADE_PE")?.toBoolean()
|
||||
?: true
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
name = "papermc"
|
||||
url = uri("https://repo.papermc.io/repository/maven-public/")
|
||||
@@ -47,6 +56,10 @@ repositories {
|
||||
includeGroup("com.github.grimanticheat")
|
||||
}
|
||||
}
|
||||
maven {
|
||||
name = "grimacSnapshots"
|
||||
url = uri("https://repo.grim.ac/snapshots")
|
||||
}
|
||||
maven("https://repo.viaversion.com") // ViaVersion
|
||||
maven("https://repo.aikar.co/content/groups/aikar/") // ACF
|
||||
maven("https://nexus.scarsz.me/content/repositories/releases") // Configuralize
|
||||
@@ -62,18 +75,24 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
compileOnly("io.papermc.paper:paper-api:1.20.6-R0.1-SNAPSHOT")
|
||||
implementation("com.github.retrooper:packetevents-spigot:2.7.1-SNAPSHOT")
|
||||
// PE dependency is now conditional
|
||||
if (shadePE) {
|
||||
implementation("com.github.retrooper:packetevents-spigot:2.8.0-SNAPSHOT")
|
||||
} else {
|
||||
compileOnly("com.github.retrooper:packetevents-spigot:2.8.0-SNAPSHOT")
|
||||
}
|
||||
implementation("co.aikar:acf-paper:0.5.1-SNAPSHOT")
|
||||
implementation("club.minnced:discord-webhooks:0.8.0") // Newer versions include kotlin-stdlib, which leads to incompatibility with plugins that use Kotlin
|
||||
implementation("it.unimi.dsi:fastutil:8.5.15")
|
||||
implementation("github.scarsz:configuralize:1.4.0")
|
||||
implementation("com.zaxxer:HikariCP:4.0.3")
|
||||
|
||||
|
||||
// Used for local testing:
|
||||
//implementation("ac.grim.grimac:GrimAPI:1.0")
|
||||
implementation("com.github.grimanticheat:grimapi:05e31d62f2")
|
||||
implementation("ac.grim.grimac:GrimAPI:05e31d62f2")
|
||||
|
||||
implementation("net.kyori:adventure-text-minimessage:4.17.0")
|
||||
implementation("net.kyori:adventure-text-minimessage:4.20.0")
|
||||
implementation("net.kyori:adventure-platform-bukkit:4.3.4")
|
||||
|
||||
implementation("org.jetbrains:annotations:24.1.0")
|
||||
@@ -91,6 +110,10 @@ bukkit {
|
||||
apiVersion = "1.13"
|
||||
foliaSupported = true
|
||||
|
||||
if (!shadePE) {
|
||||
depend = listOf("packetevents") + (depend ?: emptyList())
|
||||
}
|
||||
|
||||
softDepend = listOf(
|
||||
"ProtocolLib",
|
||||
"ProtocolSupport",
|
||||
@@ -174,7 +197,6 @@ tasks.build {
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
options.release.set(17)
|
||||
}
|
||||
|
||||
java {
|
||||
@@ -192,10 +214,15 @@ tasks.shadowJar {
|
||||
}
|
||||
|
||||
minimize()
|
||||
archiveFileName.set("${project.name}-${project.version}.jar")
|
||||
archiveFileName.set("${project.name}-${project.version}${if (shadePE) "" else "-lite"}.jar")
|
||||
if (relocate) {
|
||||
relocate("io.github.retrooper.packetevents", "ac.grim.grimac.shaded.io.github.retrooper.packetevents")
|
||||
relocate("com.github.retrooper.packetevents", "ac.grim.grimac.shaded.com.github.retrooper.packetevents")
|
||||
// Only relocate PE if we're shading it
|
||||
if (shadePE) {
|
||||
relocate("io.github.retrooper.packetevents", "ac.grim.grimac.shaded.io.github.retrooper.packetevents")
|
||||
relocate("com.github.retrooper.packetevents", "ac.grim.grimac.shaded.com.github.retrooper.packetevents")
|
||||
exclude("assets/mappings") // Exclude new PE mappings folder
|
||||
relocate("net.kyori", "ac.grim.grimac.shaded.kyori") // use PE's built-in adventure instead when not shading
|
||||
}
|
||||
relocate("co.aikar.commands", "ac.grim.grimac.shaded.acf")
|
||||
relocate("co.aikar.locale", "ac.grim.grimac.shaded.locale")
|
||||
relocate("club.minnced", "ac.grim.grimac.shaded.discord-webhooks")
|
||||
@@ -204,12 +231,13 @@ tasks.shadowJar {
|
||||
relocate("com.google.code.gson", "ac.grim.grimac.shaded.gson")
|
||||
relocate("alexh", "ac.grim.grimac.shaded.maps")
|
||||
relocate("it.unimi.dsi.fastutil", "ac.grim.grimac.shaded.fastutil")
|
||||
relocate("net.kyori", "ac.grim.grimac.shaded.kyori")
|
||||
relocate("okhttp3", "ac.grim.grimac.shaded.okhttp3")
|
||||
relocate("okio", "ac.grim.grimac.shaded.okio")
|
||||
relocate("org.yaml.snakeyaml", "ac.grim.grimac.shaded.snakeyaml")
|
||||
relocate("org.json", "ac.grim.grimac.shaded.json")
|
||||
relocate("org.intellij", "ac.grim.grimac.shaded.intellij")
|
||||
relocate("org.jetbrains", "ac.grim.grimac.shaded.jetbrains")
|
||||
relocate("com.zaxxer", "ac.grim.grimac.shaded.zaxxer")
|
||||
relocate("org.slf4j", "ac.grim.grimac.shaded.slf4j")
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -205,7 +205,7 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
@@ -2,8 +2,11 @@ package ac.grim.grimac;
|
||||
|
||||
import ac.grim.grimac.manager.*;
|
||||
import ac.grim.grimac.manager.config.BaseConfigManager;
|
||||
import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.PlayerDataManager;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@Getter
|
||||
@@ -17,22 +20,39 @@ public enum GrimAPI {
|
||||
private final PlayerDataManager playerDataManager = new PlayerDataManager();
|
||||
private final TickManager tickManager = new TickManager();
|
||||
private final GrimExternalAPI externalAPI = new GrimExternalAPI(this);
|
||||
private ViolationDatabaseManager violationDatabaseManager;
|
||||
private InitManager initManager;
|
||||
private JavaPlugin plugin;
|
||||
private boolean criticalLoadFailure = false; // Track critical failures
|
||||
|
||||
public void load(final JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.violationDatabaseManager = new ViolationDatabaseManager(plugin);
|
||||
initManager = new InitManager();
|
||||
initManager.load();
|
||||
initManager.load(); // Load all initializers
|
||||
}
|
||||
|
||||
public void start(final JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
initManager.start();
|
||||
if (initManager != null) {
|
||||
initManager.start();
|
||||
}
|
||||
|
||||
if (criticalLoadFailure) {
|
||||
LogUtil.error("GrimAC encountered one or more critical error(s) during initialization.");
|
||||
Bukkit.getPluginManager().disablePlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(final JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
initManager.stop();
|
||||
if (initManager != null) {
|
||||
initManager.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// Method to signal a critical failure
|
||||
public void signalCriticalFailure() {
|
||||
criticalLoadFailure = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package ac.grim.grimac.checks.debug;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public abstract class AbstractDebugHandler extends Check {
|
||||
|
||||
protected final GrimPlayer grimPlayer;
|
||||
|
||||
public AbstractDebugHandler(GrimPlayer grimPlayer) {
|
||||
super(grimPlayer);
|
||||
this.grimPlayer = grimPlayer;
|
||||
}
|
||||
|
||||
public GrimPlayer getPlayer() {
|
||||
return grimPlayer;
|
||||
}
|
||||
|
||||
public abstract void toggleListener(Player player);
|
||||
|
||||
public abstract boolean toggleConsoleOutput();
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package ac.grim.grimac.checks.debug;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.Pair;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* Handles debug visualization of hitboxes and reach calculations for GrimAC.
|
||||
* Sends debug data to clients through plugin messages that can be visualized by compatible clients.
|
||||
*/
|
||||
public class HitboxDebugHandler extends AbstractDebugHandler {
|
||||
|
||||
/**
|
||||
* Set of players currently listening to hitbox debug data
|
||||
*/
|
||||
private final Set<Player> listeners = new CopyOnWriteArraySet<>(new HashSet<>());
|
||||
|
||||
/**
|
||||
* Creates a new HitboxDebugHandler for the specified player
|
||||
*
|
||||
* @param grimPlayer The GrimAC player instance to debug
|
||||
*/
|
||||
public HitboxDebugHandler(GrimPlayer grimPlayer) {
|
||||
super(grimPlayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles whether a player receives hitbox debug visualization data.
|
||||
* If the player is already listening, they will be removed. If not, they will be added.
|
||||
*
|
||||
* @param player The player to toggle debug visualization for
|
||||
*/
|
||||
@Override
|
||||
public void toggleListener(Player player) {
|
||||
if (!listeners.remove(player)) listeners.add(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean toggleConsoleOutput() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends debug visualization data for reach calculations to all listening players.
|
||||
* The data includes hitboxes, target entities, look vectors, and eye heights used in reach calculations.
|
||||
*
|
||||
* @param hitboxes Map of entity IDs to their collision boxes
|
||||
* @param targetEntities Set of entity IDs that are considered targets
|
||||
* @param lookVecsAndEyeHeights List of pairs containing look vectors and their corresponding eye heights
|
||||
* @param basePos Base position before eye height adjustments
|
||||
* @param isPrediction Whether these hitboxes are from a prediction calculation
|
||||
*
|
||||
* Packet Format (Version 1):
|
||||
* - Byte: Version (0)
|
||||
* - Byte: Global flags
|
||||
* - Bit 0: isPrediction
|
||||
* - Bits 1-7: Reserved
|
||||
* - Double: Player reach/interact distance
|
||||
* - Vector3d: Base position (3 doubles)
|
||||
* - VarInt: Number of ray traces
|
||||
* - For each ray trace:
|
||||
* - Double: Eye height delta
|
||||
* - Vector3d: Look vector (3 doubles)
|
||||
* - VarInt: Number of hitboxes
|
||||
* - For each hitbox:
|
||||
* - VarInt: Entity ID
|
||||
* - Byte: Flags
|
||||
* - Bit 0: Is target entity
|
||||
* - Bit 1: Is no collision
|
||||
* - Bits 2-7: Reserved
|
||||
* - If not NoCollisionBox:
|
||||
* - Double: minX
|
||||
* - Double: minY
|
||||
* - Double: minZ
|
||||
* - Double: maxX
|
||||
* - Double: maxY
|
||||
* - Double: maxZ
|
||||
*/
|
||||
public void sendHitboxData(Map<Integer, CollisionBox> hitboxes, Set<Integer> targetEntities,
|
||||
List<Pair<Vector, Double>> lookVecsAndEyeHeights, Vector basePos,
|
||||
boolean isPrediction, double reachDistance) {
|
||||
if (!isEnabled()) return;
|
||||
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
try {
|
||||
// Version Header
|
||||
buffer.writeByte(1);
|
||||
|
||||
// Global Flags Header
|
||||
// Pack boolean flags into a single byte
|
||||
byte global_flags = 0;
|
||||
global_flags |= (isPrediction ? 1 : 0); // Bit 0: are the hitboxes from a prediction?
|
||||
// Bits 2-7 reserved for future use
|
||||
buffer.writeByte(global_flags);
|
||||
|
||||
// Write reach distance
|
||||
buffer.writeDouble(reachDistance);
|
||||
|
||||
// Write base position
|
||||
writeVector(buffer, basePos);
|
||||
|
||||
// Write number of ray traces
|
||||
ByteBufHelper.writeVarInt(buffer, lookVecsAndEyeHeights.size());
|
||||
|
||||
// Write all possible ray traces
|
||||
for (Pair<Vector, Double> pair : lookVecsAndEyeHeights) {
|
||||
Vector lookVec = pair.first();
|
||||
double eyeHeight = pair.second();
|
||||
|
||||
// Write eye height delta and look vector
|
||||
// we make them 3 meters shorter because all of the vectors passed into here are made 3 meters longer
|
||||
// then they need to be so grim can report correct reach distance for too far away hits
|
||||
buffer.writeDouble(eyeHeight);
|
||||
writeVector(buffer, lookVec);
|
||||
}
|
||||
|
||||
// Write number of hitboxes
|
||||
ByteBufHelper.writeVarInt(buffer, hitboxes.size());
|
||||
|
||||
// Write hitbox data
|
||||
for (Map.Entry<Integer, CollisionBox> entry : hitboxes.entrySet()) {
|
||||
int entityId = entry.getKey();
|
||||
CollisionBox box = entry.getValue();
|
||||
|
||||
// Write entity ID
|
||||
ByteBufHelper.writeVarInt(buffer, entityId);
|
||||
|
||||
// Pack boolean flags into a single byte
|
||||
byte flags = 0;
|
||||
flags |= (targetEntities.contains(entityId) ? 1 : 0); // Bit 0: Is target
|
||||
flags |= (box == NoCollisionBox.INSTANCE ? 2 : 0); // Bit 1: Is no collision
|
||||
// Bits 2-7 reserved for future use
|
||||
buffer.writeByte(flags);
|
||||
|
||||
// Write box coordinates if it's not a NoCollisionBox
|
||||
if ((flags & 2) == 0) {
|
||||
SimpleCollisionBox simpleCollisionBox = (SimpleCollisionBox) box;
|
||||
buffer.writeDouble(simpleCollisionBox.minX);
|
||||
buffer.writeDouble(simpleCollisionBox.minY);
|
||||
buffer.writeDouble(simpleCollisionBox.minZ);
|
||||
buffer.writeDouble(simpleCollisionBox.maxX);
|
||||
buffer.writeDouble(simpleCollisionBox.maxY);
|
||||
buffer.writeDouble(simpleCollisionBox.maxZ);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert buffer to byte array
|
||||
byte[] data = new byte[buffer.readableBytes()];
|
||||
buffer.readBytes(data);
|
||||
|
||||
// Create and send packet
|
||||
WrapperPlayServerPluginMessage packet = new WrapperPlayServerPluginMessage(
|
||||
"grim:debug_hitbox",
|
||||
data
|
||||
);
|
||||
|
||||
// Send to all listeners
|
||||
for (Player listener : listeners) {
|
||||
if (listener != null) {
|
||||
PacketEvents.getAPI().getPlayerManager().sendPacket(listener, packet);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Release buffer to prevent memory leaks
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return !listeners.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to write a Vector to the ByteBuf
|
||||
* @param buffer The buffer to write to
|
||||
* @param vector The vector to write
|
||||
*/
|
||||
private void writeVector(ByteBuf buffer, Vector vector) {
|
||||
buffer.writeDouble(vector.getX());
|
||||
buffer.writeDouble(vector.getY());
|
||||
buffer.writeDouble(vector.getZ());
|
||||
}
|
||||
}
|
||||
@@ -38,14 +38,31 @@ public class BadPacketsP extends Check implements PacketCheck {
|
||||
int button = wrapper.getButton();
|
||||
|
||||
// TODO: Adjust for containers
|
||||
boolean flag = switch (clickType) {
|
||||
case PICKUP, QUICK_MOVE, CLONE -> button > 2 || button < 0;
|
||||
case SWAP -> (button > 8 || button < 0) && button != 40;
|
||||
case THROW -> button != 0 && button != 1;
|
||||
case QUICK_CRAFT -> button == 3 || button == 7 || button > 10 || button < 0;
|
||||
case PICKUP_ALL -> button != 0;
|
||||
case UNKNOWN -> false;
|
||||
};
|
||||
boolean flag;
|
||||
switch (clickType) {
|
||||
case PICKUP:
|
||||
case QUICK_MOVE:
|
||||
case CLONE:
|
||||
flag = button > 2 || button < 0;
|
||||
break;
|
||||
case SWAP:
|
||||
flag = (button > 8 || button < 0) && button != 40;
|
||||
break;
|
||||
case THROW:
|
||||
flag = button != 0 && button != 1;
|
||||
break;
|
||||
case QUICK_CRAFT:
|
||||
flag = button == 3 || button == 7 || button > 10 || button < 0;
|
||||
break;
|
||||
case PICKUP_ALL:
|
||||
flag = button != 0;
|
||||
break;
|
||||
case UNKNOWN:
|
||||
flag = true;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Impossible clickType; Compiler does not know this is unreachable!");
|
||||
}
|
||||
|
||||
// Allowing this to false flag to debug and find issues faster
|
||||
if (flag) {
|
||||
|
||||
@@ -48,17 +48,20 @@ public class BadPacketsX extends Check implements PostPredictionCheck {
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
|
||||
switch (new WrapperPlayClientEntityAction(event).getAction()) {
|
||||
case START_SNEAKING, STOP_SNEAKING -> {
|
||||
WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event);
|
||||
switch (wrapper.getAction()) {
|
||||
case START_SNEAKING:
|
||||
case STOP_SNEAKING:
|
||||
if (sneak) {
|
||||
if (player.canSkipTicks() || flagAndAlert()) {
|
||||
flags++;
|
||||
}
|
||||
}
|
||||
|
||||
sneak = true;
|
||||
}
|
||||
case START_SPRINTING, STOP_SPRINTING -> {
|
||||
break;
|
||||
|
||||
case START_SPRINTING:
|
||||
case STOP_SPRINTING:
|
||||
if (player.inVehicle()) {
|
||||
return;
|
||||
}
|
||||
@@ -68,9 +71,12 @@ public class BadPacketsX extends Check implements PostPredictionCheck {
|
||||
flags++;
|
||||
}
|
||||
}
|
||||
|
||||
sprint = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Handle other cases if necessary
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
// Based loosely off of Hawk BlockBreakSpeedSurvival
|
||||
@@ -27,7 +28,7 @@ public class FastBreak extends Check implements BlockBreakCheck {
|
||||
|
||||
// For some reason these states flag and I don't know why.
|
||||
// Better to just exempt to not annoy legit players.
|
||||
private static final Set<StateType> EXEMPT_STATES = Set.of();
|
||||
private static final Set<StateType> EXEMPT_STATES = Collections.emptySet();
|
||||
|
||||
public FastBreak(GrimPlayer playerData) {
|
||||
super(playerData);
|
||||
|
||||
@@ -44,15 +44,30 @@ public class PositionBreakA extends Check implements BlockBreakCheck {
|
||||
|
||||
// So now we have the player's possible eye positions
|
||||
// So then look at the face that the player has clicked
|
||||
boolean flag = switch (blockBreak.face) {
|
||||
case NORTH -> eyePositions.minZ > combined.minZ; // Z- face
|
||||
case SOUTH -> eyePositions.maxZ < combined.maxZ; // Z+ face
|
||||
case EAST -> eyePositions.maxX < combined.maxX; // X+ face
|
||||
case WEST -> eyePositions.minX > combined.minX; // X- face
|
||||
case UP -> eyePositions.maxY < combined.maxY; // Y+ face
|
||||
case DOWN -> eyePositions.minY > combined.minY; // Y- face
|
||||
default -> false;
|
||||
};
|
||||
boolean flag;
|
||||
switch (blockBreak.face) {
|
||||
case NORTH:
|
||||
flag = eyePositions.minZ > combined.minZ; // Z- face
|
||||
break;
|
||||
case SOUTH:
|
||||
flag = eyePositions.maxZ < combined.maxZ; // Z+ face
|
||||
break;
|
||||
case EAST:
|
||||
flag = eyePositions.maxX < combined.maxX; // X+ face
|
||||
break;
|
||||
case WEST:
|
||||
flag = eyePositions.minX > combined.minX; // X- face
|
||||
break;
|
||||
case UP:
|
||||
flag = eyePositions.maxY < combined.maxY; // Y+ face
|
||||
break;
|
||||
case DOWN:
|
||||
flag = eyePositions.minY > combined.minY; // Y- face
|
||||
break;
|
||||
default:
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (flag && flagAndAlert("action=" + blockBreak.action + ", face=" + blockBreak.face) && shouldModifyPackets()) {
|
||||
blockBreak.cancel();
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package ac.grim.grimac.checks.impl.combat;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
|
||||
@CheckData(name = "HitboxBlock", configName = "HitboxBlock", setback = 20)
|
||||
public class HitboxBlock extends Check implements PacketCheck {
|
||||
public HitboxBlock(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ac.grim.grimac.checks.impl.combat;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
|
||||
@CheckData(name = "HitboxEntity", configName = "HitboxEntity", setback = 30)
|
||||
public class HitboxEntity extends Check implements PacketCheck {
|
||||
public HitboxEntity(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -5,9 +5,9 @@ import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
|
||||
@CheckData(name = "Hitboxes", setback = 10)
|
||||
public class Hitboxes extends Check implements PacketCheck {
|
||||
public Hitboxes(GrimPlayer player) {
|
||||
@CheckData(name = "HitboxMiss", configName = "HitboxMiss", setback = 10)
|
||||
public class HitboxMiss extends Check implements PacketCheck {
|
||||
public HitboxMiss(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,18 @@ package ac.grim.grimac.checks.impl.combat;
|
||||
import ac.grim.grimac.api.config.ConfigManager;
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.BlockHitData;
|
||||
import ac.grim.grimac.utils.data.EntityHitData;
|
||||
import ac.grim.grimac.utils.data.HitData;
|
||||
import ac.grim.grimac.utils.data.Pair;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
|
||||
import ac.grim.grimac.utils.nmsutil.BlockRayTrace;
|
||||
import ac.grim.grimac.utils.data.packetentity.dragon.PacketEntityEnderDragonPart;
|
||||
import ac.grim.grimac.utils.nmsutil.ReachUtils;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
@@ -31,11 +39,15 @@ import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.GameMode;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
@@ -47,13 +59,19 @@ public class Reach extends Check implements PacketCheck {
|
||||
// Only one flag per reach attack, per entity, per tick.
|
||||
// We store position because lastX isn't reliable on teleports.
|
||||
private final Int2ObjectMap<Vector3d> playerAttackQueue = new Int2ObjectOpenHashMap<>();
|
||||
// Used to prevent falses in the wall hit check
|
||||
private final Set<Vector3i> blocksChangedThisTick = new HashSet<>();
|
||||
|
||||
public static final double extraSearchDistance = 3; // extra distance to raytrace beyond reach distance so we know far beyond the legit distance a cheater hit
|
||||
|
||||
private static final List<EntityType> blacklisted = Arrays.asList(
|
||||
EntityTypes.BOAT,
|
||||
EntityTypes.CHEST_BOAT,
|
||||
EntityTypes.SHULKER);
|
||||
|
||||
private boolean ignoreNonPlayerTargets;
|
||||
private boolean cancelImpossibleHits;
|
||||
private double threshold;
|
||||
public double reachThreshold;
|
||||
private double cancelBuffer; // For the next 4 hits after using reach, we aggressively cancel reach
|
||||
|
||||
public Reach(GrimPlayer player) {
|
||||
@@ -85,6 +103,10 @@ public class Reach extends Check implements PacketCheck {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ignoreNonPlayerTargets && !entity.getType().equals(EntityTypes.PLAYER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dead entities cause false flags (https://github.com/GrimAnticheat/Grim/issues/546)
|
||||
if (entity.isDead) return;
|
||||
|
||||
@@ -109,8 +131,9 @@ public class Reach extends Check implements PacketCheck {
|
||||
}
|
||||
|
||||
// If the player set their look, or we know they have a new tick
|
||||
final boolean isFlying = WrapperPlayClientPlayerFlying.isFlying(event.getPacketType());
|
||||
if (isUpdate(event.getPacketType())) {
|
||||
tickBetterReachCheckWithAngle();
|
||||
tickBetterReachCheckWithAngle(isFlying);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,8 +155,7 @@ public class Reach extends Check implements PacketCheck {
|
||||
|
||||
// Filter out what we assume to be cheats
|
||||
if (cancelBuffer != 0) {
|
||||
CheckResult result = checkReach(reachEntity, new Vector3d(player.x, player.y, player.z), true);
|
||||
return result.isFlag(); // If they flagged
|
||||
return checkReach(reachEntity, new Vector3d(player.x, player.y, player.z), true) != NONE; // If they flagged
|
||||
} else {
|
||||
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
|
||||
if (reachEntity.getType() == EntityTypes.END_CRYSTAL) {
|
||||
@@ -143,25 +165,37 @@ public class Reach extends Check implements PacketCheck {
|
||||
}
|
||||
}
|
||||
|
||||
private void tickBetterReachCheckWithAngle() {
|
||||
private void tickBetterReachCheckWithAngle(boolean isFlying) {
|
||||
for (Int2ObjectMap.Entry<Vector3d> attack : playerAttackQueue.int2ObjectEntrySet()) {
|
||||
PacketEntity reachEntity = player.compensatedEntities.entityMap.get(attack.getIntKey());
|
||||
if (reachEntity == null) continue;
|
||||
|
||||
CheckResult result = checkReach(reachEntity, attack.getValue(), false);
|
||||
String added;
|
||||
switch (result.type()) {
|
||||
case REACH -> {
|
||||
String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : ", type=" + reachEntity.getType().getName().getKey();
|
||||
case REACH:
|
||||
added = reachEntity.getType() == EntityTypes.PLAYER ? "" : ", type=" + reachEntity.getType().getName().getKey();
|
||||
flagAndAlert(result.verbose() + added);
|
||||
}
|
||||
case HITBOX -> {
|
||||
String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey();
|
||||
player.checkManager.getPacketCheck(Hitboxes.class).flagAndAlert(result.verbose() + added);
|
||||
}
|
||||
break;
|
||||
case HITBOX:
|
||||
added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey();
|
||||
player.checkManager.getPacketCheck(HitboxMiss.class).flagAndAlert(result.verbose() + added);
|
||||
break;
|
||||
case BLOCK:
|
||||
added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey();
|
||||
player.checkManager.getPacketCheck(HitboxBlock.class).flagAndAlert(result.verbose() + added);
|
||||
break;
|
||||
case ENTITY:
|
||||
added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey();
|
||||
player.checkManager.getPacketCheck(HitboxEntity.class).flagAndAlert(result.verbose() + added);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
playerAttackQueue.clear();
|
||||
// We can't use transactions for this because of this problem:
|
||||
// transaction -> block changed applied -> 2nd transaction -> list cleared -> attack packet -> flying -> reach block hit checked, falses
|
||||
if (isFlying) blocksChangedThisTick.clear();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -178,7 +212,7 @@ public class Reach extends Check implements PacketCheck {
|
||||
targetBox.expand(0.1f);
|
||||
}
|
||||
|
||||
targetBox.expand(threshold);
|
||||
targetBox.expand(reachThreshold);
|
||||
|
||||
// This is better than adding to the reach, as 0.03 can cause a player to miss their target
|
||||
// Adds some more than 0.03 uncertainty in some cases, but a good trade off for simplicity
|
||||
@@ -190,28 +224,17 @@ public class Reach extends Check implements PacketCheck {
|
||||
|
||||
double minDistance = Double.MAX_VALUE;
|
||||
|
||||
// https://bugs.mojang.com/browse/MC-67665
|
||||
List<Vector> possibleLookDirs = new ArrayList<>(Collections.singletonList(ReachUtils.getLook(player, player.xRot, player.yRot)));
|
||||
|
||||
// If we are a tick behind, we don't know their next look so don't bother doing this
|
||||
if (!isPrediction) {
|
||||
possibleLookDirs.add(ReachUtils.getLook(player, player.lastXRot, player.yRot));
|
||||
|
||||
// 1.9+ players could be a tick behind because we don't get skipped ticks
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
possibleLookDirs.add(ReachUtils.getLook(player, player.lastXRot, player.lastYRot));
|
||||
}
|
||||
|
||||
// 1.7 players do not have any of these issues! They are always on the latest look vector
|
||||
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_8)) {
|
||||
possibleLookDirs = Collections.singletonList(ReachUtils.getLook(player, player.xRot, player.yRot));
|
||||
}
|
||||
}
|
||||
// will store all lookVecsAndEyeHeight pairs that landed a hit on the target entity
|
||||
// We only need to check for blocking intersections for these
|
||||
List<Pair<Vector, Double>> lookVecsAndEyeHeights = new ArrayList<>();
|
||||
|
||||
final double maxReach = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
|
||||
// +3 would be 3 + 3 = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox"
|
||||
final double distance = maxReach + 3;
|
||||
// We raytrace for > the player's reach distance so in the case a player is hacking
|
||||
// We can return in the flag the distance of the reach hit instead of a generic "player failed reach check"
|
||||
// +3 would be 3 + extraSearchDistance = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox"
|
||||
final double distance = maxReach + extraSearchDistance;
|
||||
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
|
||||
final Vector[] possibleLookDirs = player.getPossibleLookVectors(isPrediction);
|
||||
final Vector eyePos = new Vector(from.getX(), 0, from.getZ());
|
||||
for (Vector lookVec : possibleLookDirs) {
|
||||
for (double eye : possibleEyeHeights) {
|
||||
@@ -227,16 +250,47 @@ public class Reach extends Check implements PacketCheck {
|
||||
|
||||
if (intercept != null) {
|
||||
minDistance = Math.min(eyePos.distance(intercept), minDistance);
|
||||
lookVecsAndEyeHeights.add(new Pair<>(lookVec, eye));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hitboxDebuggingEnabled())
|
||||
sendHitboxDebugData(reachEntity, from, lookVecsAndEyeHeights, isPrediction);
|
||||
|
||||
HitData foundHitData = null;
|
||||
// If the entity is within range of the player (we'll flag anyway if not, so no point checking blocks in this case)
|
||||
// Ignore when could be hitting through a moving shulker, piston blocks. They are just too glitchy/uncertain to check.
|
||||
if (minDistance <= distance - extraSearchDistance && !player.compensatedWorld.isNearHardEntity(player.boundingBox.copy().expand(4))) {
|
||||
// we can optimize didRayTraceHit more to only rayTrace up to the maximize distance of all rays that hit to the target...
|
||||
// I'm too lazy to do that and we don't need to optimize that much yet so...
|
||||
final @Nullable Pair<Double, HitData> hitResult = didRayTraceHit(reachEntity, lookVecsAndEyeHeights, from);
|
||||
HitData hitData = hitResult.second();
|
||||
// If the returned hit result was NOT the target entity we flag the check
|
||||
if (hitData instanceof EntityHitData &&
|
||||
player.compensatedEntities.getPacketEntityID(((EntityHitData) hitData).getEntity()) != player.compensatedEntities.getPacketEntityID(reachEntity)) {
|
||||
minDistance = Double.MIN_VALUE;
|
||||
foundHitData = hitData;
|
||||
// until I fix block modeling exempt any blocks changed this tick
|
||||
} else if (hitData instanceof BlockHitData && !blocksChangedThisTick.contains(((BlockHitData) hitData).getPosition())) {
|
||||
minDistance = Double.MIN_VALUE;
|
||||
foundHitData = hitData;
|
||||
}
|
||||
}
|
||||
|
||||
// if the entity is not exempt and the entity is alive
|
||||
if ((!blacklisted.contains(reachEntity.getType()) && reachEntity.isLivingEntity()) || reachEntity.getType() == EntityTypes.END_CRYSTAL) {
|
||||
if (minDistance == Double.MAX_VALUE) {
|
||||
if (minDistance == Double.MIN_VALUE && foundHitData != null) {
|
||||
cancelBuffer = 1;
|
||||
if (foundHitData instanceof BlockHitData) {
|
||||
return new CheckResult(ResultType.BLOCK, "Hit block=" + ((BlockHitData) foundHitData).getState().getType().getName() + " ");
|
||||
} else { // entity hit data
|
||||
return new CheckResult(ResultType.ENTITY, "Hit entity=" + ((EntityHitData) foundHitData).getEntity().getType().getName() + " ");
|
||||
}
|
||||
} else if (minDistance == Double.MAX_VALUE) {
|
||||
cancelBuffer = 1;
|
||||
return new CheckResult(ResultType.HITBOX, "");
|
||||
} else if (minDistance > maxReach) {
|
||||
} else if (minDistance > player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE)) {
|
||||
cancelBuffer = 1;
|
||||
return new CheckResult(ResultType.REACH, String.format("%.5f", minDistance) + " blocks");
|
||||
} else {
|
||||
@@ -249,19 +303,138 @@ public class Reach extends Check implements PacketCheck {
|
||||
|
||||
private static final CheckResult NONE = new CheckResult(ResultType.NONE, "");
|
||||
|
||||
private record CheckResult(ResultType type, String verbose) {
|
||||
private static class CheckResult {
|
||||
private final ResultType type;
|
||||
private final String verbose;
|
||||
|
||||
public CheckResult(ResultType type, String verbose) {
|
||||
this.type = type;
|
||||
this.verbose = verbose;
|
||||
}
|
||||
|
||||
public ResultType type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String verbose() {
|
||||
return verbose;
|
||||
}
|
||||
|
||||
public boolean isFlag() {
|
||||
return type != ResultType.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
private enum ResultType {
|
||||
REACH, HITBOX, NONE
|
||||
REACH, HITBOX, BLOCK, ENTITY, NONE
|
||||
}
|
||||
|
||||
public void handleBlockChange(Vector3i vector3i, WrappedBlockState state) {
|
||||
if (blocksChangedThisTick.size() >= 40) return; // Don't let players freeze movement packets to grow this
|
||||
// Only do this for nearby blocks
|
||||
if (new Vector(vector3i.x, vector3i.y, vector3i.z).distanceSquared(new Vector(player.x, player.y, player.z)) > 6) return;
|
||||
// Only do this if the state really had any world impact
|
||||
if (state.equals(player.compensatedWorld.getBlock(vector3i))) return;
|
||||
blocksChangedThisTick.add(vector3i);
|
||||
}
|
||||
|
||||
// Checks if it was possible to hit a target entity
|
||||
// TODO refactor to return list of rays and why each of them didn't hit instead of closest obstruction
|
||||
// NOTE: It should be impossible for the returned Pair to be null
|
||||
// because all of the possibleLookVecsAndEyeHeights passed in should be ones that hit the target entity
|
||||
// in previous parts of this check when we didn't check for any obstructions like blocks/entities
|
||||
private @NotNull Pair<@NotNull Double, @NotNull HitData> didRayTraceHit(PacketEntity targetEntity,
|
||||
List<Pair<Vector, Double>> possibleLookVecsAndEyeHeights,
|
||||
Vector3d from) {
|
||||
HitData firstObstruction = null;
|
||||
double firstObstructionDistanceSq = 0;
|
||||
|
||||
// Check every possible look direction and every possible eye height
|
||||
for (Pair<Vector, Double> vectorDoublePair : possibleLookVecsAndEyeHeights) {
|
||||
Vector lookVec = vectorDoublePair.first();
|
||||
double eye = vectorDoublePair.second();
|
||||
|
||||
Vector eyes = new Vector(from.getX(), from.getY() + eye, from.getZ());
|
||||
// this function is completely 0.03 aware
|
||||
final HitData hitResult = BlockRayTrace.getNearestHitResult(player, targetEntity, eyes, lookVec);
|
||||
|
||||
// If we hit the target entity, it's a valid hit
|
||||
if (hitResult instanceof EntityHitData && ((EntityHitData) hitResult).getEntity().equals(targetEntity)) {
|
||||
double distanceSquared = eyes.distanceSquared(hitResult.getBlockHitLocation());
|
||||
return new Pair<>(distanceSquared, hitResult); // Legitimate hit
|
||||
} else if (hitResult != null && firstObstruction == null) {
|
||||
// Store the first obstruction only
|
||||
firstObstruction = hitResult;
|
||||
firstObstructionDistanceSq = eyes.distanceSquared(hitResult.getBlockHitLocation());
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first obstruction if no valid hit found
|
||||
// Since we sort eye heights by likeniness, we should in effect return the most likely (first) obstruction
|
||||
assert firstObstruction != null;
|
||||
return new Pair<>(firstObstructionDistanceSq, firstObstruction);
|
||||
}
|
||||
|
||||
private boolean hitboxDebuggingEnabled() {
|
||||
return player.checkManager.getCheck(HitboxDebugHandler.class).isEnabled();
|
||||
}
|
||||
|
||||
private void sendHitboxDebugData(PacketEntity reachEntity, Vector3d from, List<Pair<Vector, Double>> lookVecsAndEyeHeights, boolean isPrediction) {
|
||||
Map<Integer, CollisionBox> hitboxes = new HashMap<>();
|
||||
for (Int2ObjectMap.Entry<PacketEntity> entry : player.compensatedEntities.entityMap.int2ObjectEntrySet()) {
|
||||
PacketEntity entity = entry.getValue();
|
||||
if (!entity.canHit()) continue;
|
||||
|
||||
CollisionBox box;
|
||||
|
||||
if (entity.equals(reachEntity)) {
|
||||
// Target entity gets expanded hitbox
|
||||
box = entity.getPossibleCollisionBoxes();
|
||||
SimpleCollisionBox sBox = (SimpleCollisionBox) box;
|
||||
sBox.expand(player.checkManager.getPacketCheck(Reach.class).reachThreshold);
|
||||
|
||||
// Add movement threshold uncertainty for 1.9+ or non-position updates
|
||||
if (!player.packetStateData.didLastLastMovementIncludePosition
|
||||
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
sBox.expand(player.getMovementThreshold());
|
||||
}
|
||||
} else {
|
||||
// Non-target entities
|
||||
box = entity.getMinimumPossibleCollisionBoxes();
|
||||
if (box instanceof NoCollisionBox) {
|
||||
hitboxes.put(entry.getIntKey(), NoCollisionBox.INSTANCE);
|
||||
continue;
|
||||
} else if (box instanceof SimpleCollisionBox) {
|
||||
SimpleCollisionBox sBox = (SimpleCollisionBox) box;
|
||||
sBox.expand(-player.checkManager.getPacketCheck(Reach.class).reachThreshold);
|
||||
// Shrink non-target entities by movement threshold when applicable
|
||||
if (!player.packetStateData.didLastLastMovementIncludePosition
|
||||
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
sBox.expand(-player.getMovementThreshold());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add 1.8 and below extra hitbox size
|
||||
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)
|
||||
&& box instanceof SimpleCollisionBox) {
|
||||
((SimpleCollisionBox) box).expand(0.1f);
|
||||
}
|
||||
|
||||
hitboxes.put(entry.getIntKey(), box);
|
||||
}
|
||||
|
||||
player.checkManager.getCheck(HitboxDebugHandler.class).sendHitboxData(hitboxes,
|
||||
Collections.singleton(player.compensatedEntities.getPacketEntityID(reachEntity)),
|
||||
lookVecsAndEyeHeights,
|
||||
new Vector(from.getX(), from.getY(), from.getZ()),
|
||||
isPrediction, player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReload(ConfigManager config) {
|
||||
this.ignoreNonPlayerTargets = config.getBooleanElse("Reach.ignore-non-player-targets", false);
|
||||
this.cancelImpossibleHits = config.getBooleanElse("Reach.block-impossible-hits", true);
|
||||
this.threshold = config.getDoubleElse("Reach.threshold", 0.0005);
|
||||
this.reachThreshold = config.getDoubleElse("Reach.threshold", 0.0005);
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,6 @@ public class CrashH extends Check implements PacketCheck {
|
||||
player.onPacketCancel();
|
||||
}
|
||||
flagAndAlert("(invalid) length=" + length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ public class ExploitC extends Check implements PacketCheck {
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.CHAT_COMMAND_UNSIGNED) {
|
||||
String command = "/" + new WrapperPlayClientChatCommandUnsigned(event).getCommand();
|
||||
if (!command.stripTrailing().equals(command)) {
|
||||
String strippedCommand = command.replaceAll("\\s+$", ""); // Remove trailing whitespace
|
||||
if (!strippedCommand.equals(command)) {
|
||||
if (flagAndAlert("command=" + command)) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity.InteractAction;
|
||||
|
||||
@CheckData(name = "InventoryA", setback = 3, description = "Attacked an entity while inventory is open")
|
||||
public class InventoryA extends InventoryCheck {
|
||||
public InventoryA(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
super.onPacketReceive(event);
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
|
||||
WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event);
|
||||
|
||||
if (wrapper.getAction() != InteractAction.ATTACK) return;
|
||||
|
||||
// Is not possible to attack while the inventory is open.
|
||||
if (player.hasInventoryOpen) {
|
||||
if (flagAndAlert()) {
|
||||
// Cancel the packet
|
||||
if (shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
if (!isNoSetbackPermission())
|
||||
closeInventory();
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
|
||||
|
||||
@CheckData(name = "InventoryB", setback = 3, description = "Started digging blocks while inventory is open")
|
||||
public class InventoryB extends InventoryCheck {
|
||||
public InventoryB(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging wrapper) {
|
||||
if (wrapper.getAction() != DiggingAction.START_DIGGING) return;
|
||||
|
||||
// Is not possible to start digging a block while the inventory is open.
|
||||
if (player.hasInventoryOpen) {
|
||||
if (flagAndAlert()) {
|
||||
// Cancel the packet
|
||||
if (shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
if (!isNoSetbackPermission()) {
|
||||
closeInventory();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
|
||||
|
||||
@CheckData(name = "InventoryC", setback = 3, description = "Placed a block while inventory is open")
|
||||
public class InventoryC extends InventoryCheck {
|
||||
|
||||
public InventoryC(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
public void onBlockPlace(final BlockPlace place) {
|
||||
// It is not possible to place a block while the inventory is open
|
||||
if (player.hasInventoryOpen) {
|
||||
if (flagAndAlert()) {
|
||||
if (shouldModifyPackets()) {
|
||||
place.resync();
|
||||
}
|
||||
if (!isNoSetbackPermission()) {
|
||||
closeInventory();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import ac.grim.grimac.utils.data.VectorData;
|
||||
import ac.grim.grimac.utils.data.VectorData.MoveVectorData;
|
||||
import ac.grim.grimac.utils.data.VehicleData;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
@CheckData(name = "InventoryD", setback = 1, decay = 0.25)
|
||||
public class InventoryD extends InventoryCheck {
|
||||
private int horseJumpVerbose;
|
||||
|
||||
public InventoryD(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(final PredictionComplete predictionComplete) {
|
||||
if (!predictionComplete.isChecked() ||
|
||||
predictionComplete.getData().isTeleport() ||
|
||||
player.getSetbackTeleportUtil().blockOffsets ||
|
||||
player.packetStateData.lastPacketWasTeleport ||
|
||||
player.packetStateData.isSlowedByUsingItem() ||
|
||||
System.currentTimeMillis() - player.lastBlockPlaceUseItem < 50L) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.hasInventoryOpen) {
|
||||
boolean inVehicle = player.inVehicle();
|
||||
boolean isJumping, isMoving;
|
||||
|
||||
if (inVehicle) {
|
||||
VehicleData vehicle = player.vehicleData;
|
||||
|
||||
// Will flag once if player open anything with pressed space bar
|
||||
isJumping = vehicle.nextHorseJump > 0 && horseJumpVerbose++ >= 1;
|
||||
isMoving = vehicle.nextVehicleForward != 0 || vehicle.nextVehicleHorizontal != 0;
|
||||
} else {
|
||||
MoveVectorData move = findMovement(player.predictedVelocity);
|
||||
|
||||
isJumping = player.predictedVelocity.isJump();
|
||||
isMoving = move != null && (move.x != 0 || move.z != 0);
|
||||
}
|
||||
|
||||
if (!isMoving && !isJumping) {
|
||||
reward();
|
||||
return;
|
||||
}
|
||||
|
||||
if (flag()) {
|
||||
if (!isNoSetbackPermission())
|
||||
closeInventory();
|
||||
|
||||
StringJoiner joiner = new StringJoiner(" ");
|
||||
|
||||
if (isMoving) joiner.add("moving");
|
||||
if (isJumping) joiner.add("jumping");
|
||||
if (inVehicle) joiner.add("inVehicle");
|
||||
|
||||
alert(joiner.toString());
|
||||
}
|
||||
} else {
|
||||
horseJumpVerbose = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private MoveVectorData findMovement(VectorData vectorData) {
|
||||
if (vectorData instanceof MoveVectorData) {
|
||||
return (MoveVectorData) vectorData;
|
||||
}
|
||||
|
||||
while (vectorData != null) {
|
||||
vectorData = vectorData.lastVector;
|
||||
if (vectorData instanceof MoveVectorData) {
|
||||
return (MoveVectorData) vectorData;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
|
||||
@CheckData(name = "InventoryE", setback = 3, description = "Sent a held item change packet while inventory is open")
|
||||
public class InventoryE extends InventoryCheck {
|
||||
private long lastTransaction = Long.MAX_VALUE; // Impossible transaction ID
|
||||
|
||||
public InventoryE(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
super.onPacketReceive(event);
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) {
|
||||
// It is not possible to change hotbar slots with held item change while the inventory is open
|
||||
// A container click packet would be sent instead
|
||||
if (player.hasInventoryOpen) {
|
||||
if (this.lastTransaction < player.lastTransactionReceived.get()
|
||||
&& flagAndAlert()) {
|
||||
// Cancel the packet
|
||||
if (shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
player.getInventory().needResend = true;
|
||||
}
|
||||
if (!isNoSetbackPermission()) {
|
||||
closeInventory();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(PacketSendEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Server.HELD_ITEM_CHANGE) {
|
||||
this.lastTransaction = player.lastTransactionSent.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
|
||||
@CheckData(name = "InventoryF", setback = 3, description = "Sent a click window packet without a open inventory", experimental = true)
|
||||
public class InventoryF extends InventoryCheck {
|
||||
|
||||
public InventoryF(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
// Exempt on 1.9+ server version due to the Via hack done in PacketPlayerWindow, the exemption can be deleted
|
||||
// once we are ahead of ViaVersion
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)
|
||||
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) return;
|
||||
|
||||
super.onPacketReceive(event);
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
|
||||
if (!player.hasInventoryOpen && player.inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) {
|
||||
if (flagAndAlert()) {
|
||||
// Cancel the packet
|
||||
if (shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
if (!isNoSetbackPermission()) {
|
||||
closeInventory();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
|
||||
|
||||
@CheckData(name = "InventoryG", setback = 3, description = "Sent a entity action packet while inventory is open", experimental = true)
|
||||
public class InventoryG extends InventoryCheck {
|
||||
|
||||
public InventoryG(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (player.packetStateData.lastPacketWasTeleport) return;
|
||||
super.onPacketReceive(event);
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
|
||||
WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event);
|
||||
WrapperPlayClientEntityAction.Action action = wrapper.getAction();
|
||||
|
||||
if (action == WrapperPlayClientEntityAction.Action.STOP_SNEAKING
|
||||
|| action == WrapperPlayClientEntityAction.Action.STOP_SPRINTING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.hasInventoryOpen) {
|
||||
if (flagAndAlert() && !isNoSetbackPermission()) {
|
||||
closeInventory();
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,10 @@ import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.wrapper.configuration.client.WrapperConfigClientPluginMessage;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPluginMessage;
|
||||
import lombok.Getter;
|
||||
@@ -17,6 +18,8 @@ import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class ClientBrand extends Check implements PacketCheck {
|
||||
public static final String channel = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13) ? "minecraft:brand" : "MC|Brand";
|
||||
|
||||
@Getter
|
||||
private String brand = "vanilla";
|
||||
private boolean hasBrand = false;
|
||||
@@ -38,8 +41,7 @@ public class ClientBrand extends Check implements PacketCheck {
|
||||
|
||||
|
||||
private void handle(String channel, byte[] data) {
|
||||
final String expectedChannel = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) ? "minecraft:brand" : "MC|Brand";
|
||||
if (!channel.equals(expectedChannel)) return;
|
||||
if (!channel.equals(ClientBrand.channel)) return;
|
||||
|
||||
if (data.length > 64 || data.length == 0) {
|
||||
brand = "sent " + data.length + " bytes as brand";
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClickWindow;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClickWindow.WindowClickType;
|
||||
|
||||
@CheckData(name = "PacketOrderA", experimental = true)
|
||||
public class PacketOrderA extends Check implements PostPredictionCheck {
|
||||
public PacketOrderA(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private int invalid;
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
|
||||
final WindowClickType clickType = new WrapperPlayClientClickWindow(event).getWindowClickType();
|
||||
|
||||
if ((clickType == WindowClickType.PICKUP || clickType == WindowClickType.PICKUP_ALL) && player.packetOrderProcessor.isQuickMoveClicking()
|
||||
|| clickType == WindowClickType.QUICK_MOVE && player.packetOrderProcessor.isPickUpClicking()) {
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert() && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
invalid++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) return;
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (; invalid >= 1; invalid--) {
|
||||
flagAndAlert();
|
||||
}
|
||||
}
|
||||
|
||||
invalid = 0;
|
||||
}
|
||||
}
|
||||
+40
-10
@@ -1,4 +1,4 @@
|
||||
package ac.grim.grimac.checks.impl.badpackets;
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
@@ -9,25 +9,33 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
|
||||
|
||||
@CheckData(name = "BadPacketsM")
|
||||
public class BadPacketsM extends Check implements PacketCheck {
|
||||
public BadPacketsM(final GrimPlayer player) {
|
||||
@CheckData(name = "PacketOrderC")
|
||||
public class PacketOrderC extends Check implements PacketCheck {
|
||||
public PacketOrderC(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
// 1.7 players do not send INTERACT_AT, so we cannot check them
|
||||
private final boolean exempt = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10);
|
||||
private boolean sentInteractAt = false;
|
||||
private int requiredEntity;
|
||||
private InteractionHand requiredHand;
|
||||
private boolean requiredSneaking;
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY && !exempt) {
|
||||
if (exempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
final WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event);
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
|
||||
final WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
|
||||
|
||||
final PacketEntity entity = player.compensatedEntities.entityMap.get(wrapper.getEntityId());
|
||||
final PacketEntity entity = player.compensatedEntities.entityMap.get(packet.getEntityId());
|
||||
|
||||
// For armor stands, vanilla clients send:
|
||||
// - when renaming the armor stand or in spectator mode: INTERACT_AT + INTERACT
|
||||
@@ -35,27 +43,49 @@ public class BadPacketsM extends Check implements PacketCheck {
|
||||
// Just exempt armor stands to be safe
|
||||
if (entity != null && entity.getType() == EntityTypes.ARMOR_STAND) return;
|
||||
|
||||
switch (wrapper.getAction()) {
|
||||
final boolean sneaking = packet.isSneaking().orElse(false);
|
||||
|
||||
switch (packet.getAction()) {
|
||||
// INTERACT_AT then INTERACT
|
||||
case INTERACT:
|
||||
if (!sentInteractAt) {
|
||||
if (flagAndAlert("Missed Interact-At") && shouldModifyPackets()) {
|
||||
if (flagAndAlert("Skipped Interact-At") && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else if (packet.getEntityId() != requiredEntity || packet.getHand() != requiredHand || sneaking != requiredSneaking) {
|
||||
String verbose = "requiredEntity=" + requiredEntity + ", entity=" + packet.getEntityId()
|
||||
+ ", requiredHand=" + requiredHand + ", hand=" + packet.getHand()
|
||||
+ ", requiredSneaking=" + requiredSneaking + ", sneaking=" + sneaking;
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
}
|
||||
|
||||
sentInteractAt = false;
|
||||
break;
|
||||
case INTERACT_AT:
|
||||
if (sentInteractAt) {
|
||||
if (flagAndAlert("Missed Interact") && shouldModifyPackets()) {
|
||||
if (flagAndAlert("Skipped Interact") && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
}
|
||||
|
||||
requiredHand = packet.getHand();
|
||||
requiredEntity = packet.getEntityId();
|
||||
requiredSneaking = sneaking;
|
||||
sentInteractAt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) {
|
||||
if (sentInteractAt) {
|
||||
sentInteractAt = false;
|
||||
flagAndAlert("Skipped Interact (Tick)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
|
||||
@CheckData(name = "PacketOrderD")
|
||||
public class PacketOrderD extends Check implements PacketCheck {
|
||||
public PacketOrderD(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
@CheckData(name = "PacketOrderE", experimental = true)
|
||||
public class PacketOrderE extends Check implements PostPredictionCheck {
|
||||
public PacketOrderE(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private final ArrayDeque<String> flags = new ArrayDeque<>();
|
||||
private boolean setback;
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) {
|
||||
if (player.packetOrderProcessor.isAttacking()
|
||||
|| player.packetOrderProcessor.isRightClicking()
|
||||
|| player.packetOrderProcessor.isOpeningInventory()
|
||||
|| player.packetOrderProcessor.isReleasing()
|
||||
|| player.packetOrderProcessor.isSneaking()
|
||||
|| player.packetOrderProcessor.isSprinting()
|
||||
|| player.packetOrderProcessor.isLeavingBed()
|
||||
|| player.packetOrderProcessor.isStartingToGlide()
|
||||
|| player.packetOrderProcessor.isJumpingWithMount()
|
||||
) {
|
||||
String verbose = "attacking=" + player.packetOrderProcessor.isAttacking()
|
||||
+ ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
|
||||
+ ", openingInventory=" + player.packetOrderProcessor.isOpeningInventory()
|
||||
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
|
||||
+ ", sneaking=" + player.packetOrderProcessor.isSneaking()
|
||||
+ ", sprinting=" + player.packetOrderProcessor.isSprinting()
|
||||
+ ", bed=" + player.packetOrderProcessor.isLeavingBed()
|
||||
+ ", sprinting=" + player.packetOrderProcessor.isSprinting()
|
||||
+ ", gliding=" + player.packetOrderProcessor.isStartingToGlide()
|
||||
+ ", mountJumping=" + player.packetOrderProcessor.isJumpingWithMount();
|
||||
if (player.canSkipTicks() && flags.add(verbose) || flagAndAlert(verbose)) {
|
||||
if (player.packetOrderProcessor.isUsing()) {
|
||||
setback = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) {
|
||||
if (setback) {
|
||||
setback = false;
|
||||
setbackIfAboveSetbackVL();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (String verbose : flags) {
|
||||
if (flagAndAlert(verbose) && setback) {
|
||||
setback = false;
|
||||
setbackIfAboveSetbackVL();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setback = false;
|
||||
flags.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
@CheckData(name = "PacketOrderF", experimental = true)
|
||||
public class PacketOrderF extends Check implements PostPredictionCheck {
|
||||
public PacketOrderF(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private final ArrayDeque<String> flags = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY
|
||||
|| event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT
|
||||
|| event.getPacketType() == PacketType.Play.Client.USE_ITEM
|
||||
|| event.getPacketType() == PacketType.Play.Client.PICK_ITEM
|
||||
|| event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING
|
||||
|| (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS
|
||||
&& new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.OPEN_INVENTORY_ACHIEVEMENT)
|
||||
) if (player.packetOrderProcessor.isSprinting() || player.packetOrderProcessor.isSneaking()) {
|
||||
String verbose = "action=" + (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY ? "interact"
|
||||
: event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT ? "place"
|
||||
: event.getPacketType() == PacketType.Play.Client.USE_ITEM ? "use"
|
||||
: event.getPacketType() == PacketType.Play.Client.PICK_ITEM ? "pick"
|
||||
: event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING ? "dig"
|
||||
: "openInventory")
|
||||
+ ", sprinting=" + player.packetOrderProcessor.isSprinting()
|
||||
+ ", sneaking=" + player.packetOrderProcessor.isSneaking();
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING
|
||||
&& new WrapperPlayClientPlayerDigging(event).getAction() == DiggingAction.RELEASE_USE_ITEM
|
||||
) return; // don't cause a noslow
|
||||
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
flags.add(verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) return;
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (String verbose : flags) {
|
||||
flagAndAlert(verbose);
|
||||
}
|
||||
}
|
||||
|
||||
flags.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
@CheckData(name = "PacketOrderG", experimental = true)
|
||||
public class PacketOrderG extends Check implements PostPredictionCheck {
|
||||
public PacketOrderG(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private final ArrayDeque<String> flags = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING || (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS
|
||||
&& new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.OPEN_INVENTORY_ACHIEVEMENT)) {
|
||||
DiggingAction action = null;
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
|
||||
action = new WrapperPlayClientPlayerDigging(event).getAction();
|
||||
if (action == DiggingAction.RELEASE_USE_ITEM
|
||||
|| action == DiggingAction.START_DIGGING
|
||||
|| action == DiggingAction.CANCELLED_DIGGING
|
||||
|| action == DiggingAction.FINISHED_DIGGING
|
||||
) return;
|
||||
}
|
||||
|
||||
if (player.packetOrderProcessor.isAttacking()
|
||||
|| player.packetOrderProcessor.isReleasing()
|
||||
|| player.packetOrderProcessor.isRightClicking()
|
||||
|| player.packetOrderProcessor.isPicking()
|
||||
|| player.packetOrderProcessor.isDigging()
|
||||
) {
|
||||
String verbose = "action=" + (action == null ? "openInventory" : action == DiggingAction.SWAP_ITEM_WITH_OFFHAND ? "swap" : "drop")
|
||||
+ ", attacking=" + player.packetOrderProcessor.isAttacking()
|
||||
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
|
||||
+ ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
|
||||
+ ", picking=" + player.packetOrderProcessor.isPicking()
|
||||
+ ", digging=" + player.packetOrderProcessor.isDigging();
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
flags.add(verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) return;
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (String verbose : flags) {
|
||||
flagAndAlert(verbose);
|
||||
}
|
||||
}
|
||||
|
||||
flags.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
|
||||
|
||||
@CheckData(name = "PacketOrderH", experimental = true)
|
||||
public class PacketOrderH extends Check implements PostPredictionCheck {
|
||||
public PacketOrderH(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private int invalid;
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
|
||||
switch (new WrapperPlayClientEntityAction(event).getAction()) {
|
||||
case START_SPRINTING:
|
||||
case STOP_SPRINTING:
|
||||
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2) && player.packetOrderProcessor.isSneaking()) {
|
||||
if (!player.canSkipTicks()) {
|
||||
flagAndAlert();
|
||||
} else {
|
||||
invalid++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case START_SNEAKING:
|
||||
case STOP_SNEAKING:
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2) && player.packetOrderProcessor.isSprinting()) {
|
||||
if (!player.canSkipTicks()) {
|
||||
flagAndAlert();
|
||||
} else {
|
||||
invalid++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) return;
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (; invalid >= 1; invalid--) {
|
||||
flagAndAlert();
|
||||
}
|
||||
}
|
||||
|
||||
invalid = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.api.config.ConfigManager;
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import ac.grim.grimac.utils.nmsutil.BlockBreakSpeed;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.GameMode;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
@CheckData(name = "PacketOrderI", experimental = true)
|
||||
public class PacketOrderI extends Check implements PostPredictionCheck {
|
||||
public PacketOrderI(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private boolean exemptPlacingWhileDigging;
|
||||
|
||||
private boolean setback;
|
||||
private boolean digging; // for placing
|
||||
private final ArrayDeque<String> flags = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
|
||||
if (new WrapperPlayClientInteractEntity(event).getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
|
||||
if (player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isReleasing() || player.packetOrderProcessor.isDigging()) {
|
||||
String verbose = "type=attack, rightClicking=" + player.packetOrderProcessor.isRightClicking()
|
||||
+ ", picking=" + player.packetOrderProcessor.isPicking()
|
||||
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
|
||||
+ ", digging=" + player.packetOrderProcessor.isDigging();
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
flags.add(verbose);
|
||||
}
|
||||
}
|
||||
} else if (player.packetOrderProcessor.isReleasing() || player.packetOrderProcessor.isDigging()) {
|
||||
String verbose = "type=interact, releasing=" + player.packetOrderProcessor.isReleasing() + ", digging=" + player.packetOrderProcessor.isDigging();
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
flags.add(verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT || event.getPacketType() == PacketType.Play.Client.USE_ITEM) {
|
||||
if (player.packetOrderProcessor.isReleasing() || digging) {
|
||||
String verbose = "type=place/use, releasing=" + player.packetOrderProcessor.isReleasing() + ", digging=" + digging;
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
flags.add(verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
|
||||
WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
|
||||
|
||||
switch (packet.getAction()) {
|
||||
case RELEASE_USE_ITEM:
|
||||
if (player.packetOrderProcessor.isAttacking() || player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isDigging()) {
|
||||
String verbose = "type=release, attacking=" + player.packetOrderProcessor.isAttacking()
|
||||
+ ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
|
||||
+ ", picking=" + player.packetOrderProcessor.isPicking()
|
||||
+ ", digging=" + player.packetOrderProcessor.isDigging();
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert(verbose)) {
|
||||
setback = true;
|
||||
}
|
||||
} else {
|
||||
flags.add(verbose);
|
||||
setback = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case START_DIGGING:
|
||||
double damage = BlockBreakSpeed.getBlockDamage(player, packet.getBlockPosition());
|
||||
if (damage >= 1 || damage <= 0 && player.gamemode == GameMode.CREATIVE) {
|
||||
return;
|
||||
}
|
||||
case CANCELLED_DIGGING:
|
||||
case FINISHED_DIGGING:
|
||||
if (exemptPlacingWhileDigging || player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10)) {
|
||||
return;
|
||||
}
|
||||
digging = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isTickPacket(event.getPacketType())) {
|
||||
digging = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) {
|
||||
if (setback) {
|
||||
setbackIfAboveSetbackVL();
|
||||
setback = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (String verbose : flags) {
|
||||
if (flagAndAlert(verbose) && setback) {
|
||||
setbackIfAboveSetbackVL();
|
||||
setback = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flags.clear();
|
||||
setback = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReload(ConfigManager config) {
|
||||
exemptPlacingWhileDigging = config.getBooleanElse(getConfigName() + ".exempt-placing-while-digging", false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
|
||||
@CheckData(name = "PacketOrderJ", experimental = true)
|
||||
public class PacketOrderJ extends Check implements PostPredictionCheck {
|
||||
public PacketOrderJ(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private int invalid;
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT || event.getPacketType() == PacketType.Play.Client.USE_ITEM) {
|
||||
if (player.packetOrderProcessor.isAttacking() && !player.packetOrderProcessor.isInteracting()) {
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert() && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
invalid++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) return;
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (; invalid >= 1; invalid--) {
|
||||
flagAndAlert();
|
||||
}
|
||||
}
|
||||
|
||||
invalid = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
@CheckData(name = "PacketOrderK", experimental = true)
|
||||
public class PacketOrderK extends Check implements PostPredictionCheck {
|
||||
public PacketOrderK(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private final ArrayDeque<String> flags = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS) {
|
||||
if (new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.OPEN_INVENTORY_ACHIEVEMENT) {
|
||||
if (player.packetOrderProcessor.isClickingInInventory() || player.packetOrderProcessor.isClosingInventory()) {
|
||||
String verbose = "open, clicking=" + player.packetOrderProcessor.isClickingInInventory() + ", closing=" + player.packetOrderProcessor.isClosingInventory();
|
||||
if (!player.canSkipTicks()) {
|
||||
flagAndAlert(verbose);
|
||||
} else {
|
||||
flags.add(verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW || event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
|
||||
if (player.packetOrderProcessor.isOpeningInventory()) {
|
||||
String verbose = event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW ? "click" : "close";
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
flags.add(verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) return;
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (String verbose : flags) {
|
||||
flagAndAlert(verbose);
|
||||
}
|
||||
}
|
||||
|
||||
flags.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
@CheckData(name = "PacketOrderL", experimental = true)
|
||||
public class PacketOrderL extends Check implements PostPredictionCheck {
|
||||
public PacketOrderL(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private final ArrayDeque<String> flags = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS) {
|
||||
if (new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.OPEN_INVENTORY_ACHIEVEMENT) {
|
||||
if (player.packetOrderProcessor.isDropping()) {
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert("inventory") && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
flags.add("inventory");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
|
||||
if (new WrapperPlayClientPlayerDigging(event).getAction() == DiggingAction.SWAP_ITEM_WITH_OFFHAND) {
|
||||
if (player.packetOrderProcessor.isDropping()) {
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert("swap") && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
flags.add("swap");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) return;
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (String verbose : flags) {
|
||||
flagAndAlert(verbose);
|
||||
}
|
||||
}
|
||||
|
||||
flags.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerBlockPlacement;
|
||||
|
||||
@CheckData(name = "PacketOrderM", experimental = true)
|
||||
public class PacketOrderM extends Check implements PostPredictionCheck {
|
||||
public PacketOrderM(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private int invalid;
|
||||
private boolean usingWithoutInteract, interacting;
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
|
||||
if (new WrapperPlayClientInteractEntity(event).getAction() != WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
|
||||
interacting = true;
|
||||
if (usingWithoutInteract) {
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert() && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
} else {
|
||||
invalid++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.USE_ITEM
|
||||
|| event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT
|
||||
&& new WrapperPlayClientPlayerBlockPlacement(event).getFace() == BlockFace.OTHER) {
|
||||
if (!interacting) {
|
||||
usingWithoutInteract = true;
|
||||
}
|
||||
|
||||
interacting = false;
|
||||
}
|
||||
|
||||
if (isTickPacket(event.getPacketType())) {
|
||||
usingWithoutInteract = interacting = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) return;
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (; invalid >= 1; invalid--) {
|
||||
flagAndAlert();
|
||||
}
|
||||
}
|
||||
|
||||
invalid = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.BlockPlaceCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerBlockPlacement;
|
||||
|
||||
@CheckData(name = "PacketOrderN", experimental = true)
|
||||
public class PacketOrderN extends BlockPlaceCheck {
|
||||
public PacketOrderN(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private int invalid;
|
||||
private boolean usingWithoutPlacing, placing;
|
||||
|
||||
@Override
|
||||
public void onBlockPlace(BlockPlace place) {
|
||||
placing = true;
|
||||
if (usingWithoutPlacing) {
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert() && shouldModifyPackets() && shouldCancel()) {
|
||||
place.resync();
|
||||
}
|
||||
} else {
|
||||
invalid++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.USE_ITEM
|
||||
|| event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT
|
||||
&& new WrapperPlayClientPlayerBlockPlacement(event).getFace() == BlockFace.OTHER) {
|
||||
if (!placing) {
|
||||
usingWithoutPlacing = true;
|
||||
}
|
||||
|
||||
placing = false;
|
||||
}
|
||||
|
||||
if (isTickPacket(event.getPacketType())) {
|
||||
usingWithoutPlacing = placing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(PredictionComplete predictionComplete) {
|
||||
if (!player.canSkipTicks()) return;
|
||||
|
||||
if (player.isTickingReliablyFor(3)) {
|
||||
for (; invalid >= 1; invalid--) {
|
||||
flagAndAlert();
|
||||
}
|
||||
}
|
||||
|
||||
invalid = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity.InteractAction;
|
||||
|
||||
@CheckData(name = "PacketOrderO", experimental = true)
|
||||
public class PacketOrderO extends Check implements PacketCheck {
|
||||
public PacketOrderO(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private boolean sentMainhand;
|
||||
private int requiredEntity;
|
||||
private boolean requiredSneaking;
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
final WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
|
||||
InteractAction action = packet.getAction();
|
||||
if (action != InteractAction.ATTACK) {
|
||||
final boolean sneaking = packet.isSneaking().orElse(false);
|
||||
final int entity = packet.getEntityId();
|
||||
|
||||
if (packet.getHand() == InteractionHand.OFF_HAND) {
|
||||
if (action == InteractAction.INTERACT) {
|
||||
if (!sentMainhand) {
|
||||
if (flagAndAlert("Skipped Mainhand") && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
}
|
||||
sentMainhand = false;
|
||||
} else if (sneaking != requiredSneaking || entity != requiredEntity) {
|
||||
String verbose = "requiredEntity=" + requiredEntity + ", entity=" + entity
|
||||
+ ", requiredSneaking=" + requiredSneaking + ", sneaking=" + sneaking;
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
requiredEntity = entity;
|
||||
requiredSneaking = sneaking;
|
||||
sentMainhand = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isTickPacket(event.getPacketType())) {
|
||||
sentMainhand = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity.InteractAction;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
|
||||
|
||||
@CheckData(name = "PacketOrderP", experimental = true)
|
||||
public class PacketOrderP extends Check implements PacketCheck {
|
||||
public PacketOrderP(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private boolean sentMainhand;
|
||||
private int requiredEntity;
|
||||
private boolean requiredSneaking;
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
final WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
|
||||
InteractAction action = packet.getAction();
|
||||
if (action != InteractAction.ATTACK) {
|
||||
final boolean sneaking = packet.isSneaking().orElse(false);
|
||||
final int entity = packet.getEntityId();
|
||||
|
||||
if (packet.getHand() == InteractionHand.OFF_HAND) {
|
||||
if (action == InteractAction.INTERACT) {
|
||||
if (!sentMainhand) {
|
||||
if (flagAndAlert("Skipped Mainhand") && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
}
|
||||
sentMainhand = false;
|
||||
} else if (sneaking != requiredSneaking || entity != requiredEntity) {
|
||||
String verbose = "requiredEntity=" + requiredEntity + ", entity=" + entity
|
||||
+ ", requiredSneaking=" + requiredSneaking + ", sneaking=" + sneaking;
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
requiredEntity = entity;
|
||||
requiredSneaking = sneaking;
|
||||
sentMainhand = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && !player.packetStateData.lastPacketWasTeleport && !player.packetStateData.lastPacketWasOnePointSeventeenDuplicate) {
|
||||
sentMainhand = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.*;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
@Getter
|
||||
public final class PacketOrderProcessor extends Check implements PacketCheck {
|
||||
public PacketOrderProcessor(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private boolean openingInventory; // only pre 1.9 clients on pre 1.9 servers
|
||||
private boolean swapping;
|
||||
private boolean dropping;
|
||||
private boolean interacting;
|
||||
private boolean attacking;
|
||||
private boolean releasing;
|
||||
private boolean digging;
|
||||
private boolean sprinting;
|
||||
private boolean sneaking;
|
||||
private boolean placing;
|
||||
private boolean using;
|
||||
private boolean picking;
|
||||
private boolean clickingInInventory;
|
||||
private boolean closingInventory;
|
||||
private boolean quickMoveClicking;
|
||||
private boolean pickUpClicking;
|
||||
private boolean leavingBed;
|
||||
private boolean startingToGlide;
|
||||
private boolean jumpingWithMount;
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
final PacketTypeCommon packetType = event.getPacketType();
|
||||
|
||||
if (packetType == PacketType.Play.Client.CLIENT_STATUS) {
|
||||
if (new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.OPEN_INVENTORY_ACHIEVEMENT) {
|
||||
openingInventory = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (packetType == PacketType.Play.Client.INTERACT_ENTITY) {
|
||||
if (new WrapperPlayClientInteractEntity(event).getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
|
||||
attacking = true;
|
||||
} else {
|
||||
interacting = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (packetType == PacketType.Play.Client.PLAYER_DIGGING) {
|
||||
switch (new WrapperPlayClientPlayerDigging(event).getAction()) {
|
||||
case SWAP_ITEM_WITH_OFFHAND:
|
||||
swapping = true;
|
||||
break;
|
||||
case DROP_ITEM:
|
||||
case DROP_ITEM_STACK:
|
||||
dropping = true;
|
||||
break;
|
||||
case RELEASE_USE_ITEM:
|
||||
releasing = true;
|
||||
break;
|
||||
case FINISHED_DIGGING:
|
||||
case CANCELLED_DIGGING:
|
||||
case START_DIGGING:
|
||||
digging = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packetType == PacketType.Play.Client.ENTITY_ACTION) {
|
||||
switch (new WrapperPlayClientEntityAction(event).getAction()) {
|
||||
case START_SPRINTING:
|
||||
case STOP_SPRINTING:
|
||||
sprinting = true;
|
||||
break;
|
||||
case STOP_SNEAKING:
|
||||
case START_SNEAKING:
|
||||
sneaking = true;
|
||||
break;
|
||||
case LEAVE_BED:
|
||||
leavingBed = true;
|
||||
break;
|
||||
case START_FLYING_WITH_ELYTRA:
|
||||
startingToGlide = true;
|
||||
break;
|
||||
case OPEN_HORSE_INVENTORY:
|
||||
openingInventory = true;
|
||||
break;
|
||||
case START_JUMPING_WITH_HORSE:
|
||||
case STOP_JUMPING_WITH_HORSE:
|
||||
jumpingWithMount = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packetType == PacketType.Play.Client.USE_ITEM) {
|
||||
using = true;
|
||||
}
|
||||
|
||||
if (packetType == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT) {
|
||||
if (new WrapperPlayClientPlayerBlockPlacement(event).getFace() == BlockFace.OTHER) {
|
||||
using = true;
|
||||
} else {
|
||||
placing = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (packetType == PacketType.Play.Client.PICK_ITEM) {
|
||||
picking = true;
|
||||
}
|
||||
|
||||
if (packetType == PacketType.Play.Client.CLICK_WINDOW) {
|
||||
clickingInInventory = true;
|
||||
|
||||
switch (new WrapperPlayClientClickWindow(event).getWindowClickType()) {
|
||||
case QUICK_MOVE:
|
||||
quickMoveClicking = true;
|
||||
break;
|
||||
case PICKUP:
|
||||
case PICKUP_ALL:
|
||||
pickUpClicking = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packetType == PacketType.Play.Client.CLOSE_WINDOW) {
|
||||
closingInventory = true;
|
||||
}
|
||||
|
||||
if (isTickPacket(packetType)) {
|
||||
openingInventory = false;
|
||||
swapping = false;
|
||||
dropping = false;
|
||||
attacking = false;
|
||||
interacting = false;
|
||||
releasing = false;
|
||||
digging = false;
|
||||
placing = false;
|
||||
using = false;
|
||||
picking = false;
|
||||
sprinting = false;
|
||||
sneaking = false;
|
||||
clickingInInventory = false;
|
||||
closingInventory = false;
|
||||
quickMoveClicking = false;
|
||||
pickUpClicking = false;
|
||||
leavingBed = false;
|
||||
startingToGlide = false;
|
||||
jumpingWithMount = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public boolean isRightClicking() {
|
||||
return placing || using || interacting;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package ac.grim.grimac.checks.impl.prediction;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.type.PostPredictionCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.checks.debug.AbstractDebugHandler;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import ac.grim.grimac.utils.lists.EvictingQueue;
|
||||
@@ -14,7 +14,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
public class DebugHandler extends Check implements PostPredictionCheck {
|
||||
public class DebugHandler extends AbstractDebugHandler implements PostPredictionCheck {
|
||||
|
||||
Set<Player> listeners = new CopyOnWriteArraySet<>(new HashSet<>());
|
||||
boolean outputToConsole = false;
|
||||
|
||||
@@ -66,7 +66,9 @@ public class AirLiquidPlace extends BlockPlaceCheck {
|
||||
// But for such tiny differences in legitness its not worth it.
|
||||
Iterable<BlockModification> blockModifications = player.blockHistory.getRecentModifications((blockModification) -> currentTick - blockModification.tick() < 2
|
||||
&& blockPos.equals(blockModification.location())
|
||||
&& (blockModification.cause() == BlockModification.Cause.START_DIGGING || blockModification.cause() == BlockModification.Cause.HANDLE_NETTY_SYNC_TRANSACTION));
|
||||
&& (blockModification.cause() == BlockModification.Cause.START_DIGGING
|
||||
|| blockModification.cause() == BlockModification.Cause.HANDLE_NETTY_SYNC_TRANSACTION
|
||||
|| blockModification.cause() == BlockModification.Cause.FINISHED_DIGGING));
|
||||
|
||||
// Check if old block from instant breaking in same tick as the current placement was valid
|
||||
// There should only be one block here for legit clients
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package ac.grim.grimac.checks.impl.scaffolding;
|
||||
|
||||
import ac.grim.grimac.api.config.ConfigManager;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.BlockPlaceCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.BlockHitData;
|
||||
import ac.grim.grimac.utils.nmsutil.BlockRayTrace;
|
||||
import ac.grim.grimac.utils.nmsutil.ReachUtilsPrimitives;
|
||||
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.GameMode;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import com.viaversion.viaversion.util.Triple;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@CheckData(name = "LineOfSightPlace", experimental = true)
|
||||
public class LineOfSightPlace extends BlockPlaceCheck {
|
||||
|
||||
private double flagBuffer = 0; // If the player flags once, force them to play legit, or we will cancel the tick before.
|
||||
private boolean ignorePost = false;
|
||||
private boolean useBlockWhitelist;
|
||||
private HashSet<StateType> blockWhitelist;
|
||||
|
||||
// 15 is the maximum set of Collision boxes that will be used in the ray trace check
|
||||
// it corresponds to the size of the collision boxes from the modern Cauldron
|
||||
// Since this is used per-player we can avoid calling new[] by allocating it per-player in the check
|
||||
private final SimpleCollisionBox[] collisionBoxBuffer = new SimpleCollisionBox[15];
|
||||
|
||||
public final Set<Triple<Vector3i, WrappedBlockState, Byte>> blocksChangedList = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public LineOfSightPlace(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockPlace(final BlockPlace place) {
|
||||
if (checkIfShouldSkip(place)) return;
|
||||
|
||||
if (flagBuffer > 0 && !didRayTraceHit(place)) {
|
||||
ignorePost = true;
|
||||
// If the player hit and has flagged this check recently
|
||||
if (flagAndAlert("pre-flying: " + player.compensatedWorld.getBlock(place.getPlacedAgainstBlockLocation()).getType()) && shouldModifyPackets() && shouldCancel()) {
|
||||
place.resync(); // Deny the block placement.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use post flying because it has the correct rotation, and can't false easily.
|
||||
@Override
|
||||
public void onPostFlyingBlockPlace(BlockPlace place) {
|
||||
if (checkIfShouldSkip(place)) return;
|
||||
|
||||
// Don't flag twice
|
||||
if (ignorePost) {
|
||||
ignorePost = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ray trace to try and hit the target block.
|
||||
boolean hit = didRayTraceHit(place);
|
||||
// This can false with rapidly moving yaw in 1.8+ clients
|
||||
if (!hit) {
|
||||
flagBuffer = 1;
|
||||
flagAndAlert("post-flying: " + player.compensatedWorld.getBlock(place.getPlacedAgainstBlockLocation()).getType());
|
||||
} else {
|
||||
flagBuffer = Math.max(0, flagBuffer - 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkIfShouldSkip(BlockPlace place) {
|
||||
StateType targetBlockStateType = player.compensatedWorld.getBlock(place.getPlacedAgainstBlockLocation()).getType();
|
||||
if (player.gamemode == GameMode.SPECTATOR) return true; // A waste to check creative mode players
|
||||
if (targetBlockStateType == StateTypes.REDSTONE_WIRE) return true; // Redstone too buggy
|
||||
if (player.compensatedWorld.isNearHardEntity(player.boundingBox.copy().expand(4))) return true; // Shulkers and Pistons are too buggy
|
||||
|
||||
if (useBlockWhitelist) {
|
||||
return !isBlockTypeWhitelisted(targetBlockStateType);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean didRayTraceHit(BlockPlace place) {
|
||||
double[] possibleEyeHeights = player.getPossibleEyeHeights();
|
||||
|
||||
// Start checking if player is in the block
|
||||
double minEyeHeight = Double.MAX_VALUE;
|
||||
double maxEyeHeight = Double.MIN_VALUE;
|
||||
for (double height : possibleEyeHeights) {
|
||||
minEyeHeight = Math.min(minEyeHeight, height);
|
||||
maxEyeHeight = Math.max(maxEyeHeight, height);
|
||||
}
|
||||
|
||||
double movementThreshold = player.getMovementThreshold();
|
||||
|
||||
SimpleCollisionBox eyePositions = new SimpleCollisionBox(player.x, player.y + minEyeHeight, player.z, player.x, player.y + maxEyeHeight, player.z);
|
||||
eyePositions.expand(movementThreshold);
|
||||
|
||||
int[] interactBlockVec = new int[]{place.blockPosition.x, place.blockPosition.y, place.blockPosition.z};
|
||||
BlockFace expectedBlockFace = place.getDirection();
|
||||
|
||||
// If the player is inside a block, then they can ray trace through the block and hit the other side of the block
|
||||
// This may potentially be exploitable as a minor bypass
|
||||
if (eyePositions.isIntersected(new SimpleCollisionBox(interactBlockVec[0], interactBlockVec[1], interactBlockVec[2]))) {
|
||||
return true;
|
||||
}
|
||||
// End checking if the player is in the block
|
||||
float[][] possibleLookDirs;
|
||||
// 1.9+ players could be a tick behind because we don't get skipped ticks
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
possibleLookDirs = new float[][]{
|
||||
{player.xRot, player.yRot},
|
||||
{player.lastXRot, player.lastYRot},
|
||||
{player.lastXRot, player.yRot}
|
||||
};
|
||||
} else if (player.getClientVersion().isOlderThan(ClientVersion.V_1_8)) {
|
||||
// 1.7 players do not have any of these issues! They are always on the latest look vector
|
||||
possibleLookDirs = new float[][]{{player.xRot, player.yRot}};
|
||||
} else {
|
||||
possibleLookDirs = new float[][]{
|
||||
{player.xRot, player.yRot},
|
||||
{player.lastXRot, player.yRot}
|
||||
};
|
||||
}
|
||||
|
||||
// We do not need to add 0.03/0.0002 to maxDistance to ensure our raytrace hits blocks
|
||||
// Since we expand the hitboxes of the expectedTargetBlock by 0.03/0.002 already later
|
||||
double maxDistance = player.compensatedEntities.self.getAttributeValue(Attributes.PLAYER_BLOCK_INTERACTION_RANGE);
|
||||
|
||||
// Define possible offsets
|
||||
// TODO, vectorize this with SIMD or AVX for performance
|
||||
double[][] offsets = {
|
||||
{0, 0, 0},
|
||||
{movementThreshold, 0, 0},
|
||||
{-movementThreshold, 0, 0},
|
||||
{0, movementThreshold, 0},
|
||||
{0, -movementThreshold, 0},
|
||||
{0, 0, movementThreshold},
|
||||
{0, 0, -movementThreshold}
|
||||
};
|
||||
|
||||
double[] eyePosition = new double[3];
|
||||
double[] eyeLookDir = new double[3];
|
||||
|
||||
for (double eyeHeight : possibleEyeHeights) {
|
||||
for (float[] lookDir : possibleLookDirs) {
|
||||
for (double[] offset : offsets) {
|
||||
eyePosition[0] = player.x + offset[0];
|
||||
eyePosition[1] = player.y + eyeHeight + offset[1];
|
||||
eyePosition[2] = player.z + offset[2];
|
||||
|
||||
ReachUtilsPrimitives.getLook(player, lookDir[0], lookDir[1], eyeLookDir);
|
||||
|
||||
if (didRayTraceHitTargetBlock(eyePosition, eyeLookDir, maxDistance, interactBlockVec, expectedBlockFace)) {
|
||||
return true; // If any possible face matches the client-side placement, assume it's legitimate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // No matching face found
|
||||
}
|
||||
|
||||
private boolean didRayTraceHitTargetBlock(double[] eyePos, double[] eyeDir, double maxDistance, int[] targetBlockVec, BlockFace expectedBlockFace) {
|
||||
BlockHitData hitData = BlockRayTrace.getNearestHitResult(player, eyePos, eyeDir, maxDistance, maxDistance, targetBlockVec, expectedBlockFace, collisionBoxBuffer, false);
|
||||
|
||||
// we check for hitdata != null because of being in expanded hitbox, or there was no result, do we still need this?
|
||||
return hitData != null && hitData.success;
|
||||
}
|
||||
|
||||
private boolean isBlockTypeWhitelisted(StateType type) {
|
||||
return blockWhitelist.contains(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReload(ConfigManager config) {
|
||||
useBlockWhitelist = config.getBooleanElse("LineOfSightPlace.use-block-whitelist", false);
|
||||
blockWhitelist = new HashSet<>();
|
||||
List<String> blocks = config.getStringList("LineOfSightPlace.block-whitelist");
|
||||
for (String block : blocks) {
|
||||
blockWhitelist.add(StateTypes.getByName(block));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,15 +46,27 @@ public class PositionPlace extends BlockPlaceCheck {
|
||||
|
||||
// So now we have the player's possible eye positions
|
||||
// So then look at the face that the player has clicked
|
||||
boolean flag = switch (place.getDirection()) {
|
||||
case NORTH -> eyePositions.minZ > combined.minZ; // Z- face
|
||||
case SOUTH -> eyePositions.maxZ < combined.maxZ; // Z+ face
|
||||
case EAST -> eyePositions.maxX < combined.maxX; // X+ face
|
||||
case WEST -> eyePositions.minX > combined.minX; // X- face
|
||||
case UP -> eyePositions.maxY < combined.maxY; // Y+ face
|
||||
case DOWN -> eyePositions.minY > combined.minY; // Y- face
|
||||
default -> false;
|
||||
};
|
||||
boolean flag = false;
|
||||
switch (place.getDirection()) {
|
||||
case NORTH: // Z- face
|
||||
flag = eyePositions.minZ > combined.minZ;
|
||||
break;
|
||||
case SOUTH: // Z+ face
|
||||
flag = eyePositions.maxZ < combined.maxZ;
|
||||
break;
|
||||
case EAST: // X+ face
|
||||
flag = eyePositions.maxX < combined.maxX;
|
||||
break;
|
||||
case WEST: // X- face
|
||||
flag = eyePositions.minX > combined.minX;
|
||||
break;
|
||||
case UP: // Y+ face
|
||||
flag = eyePositions.maxY < combined.maxY;
|
||||
break;
|
||||
case DOWN: // Y- face
|
||||
flag = eyePositions.minY > combined.minY;
|
||||
break;
|
||||
}
|
||||
|
||||
if (flag && flagAndAlert() && shouldModifyPackets() && shouldCancel()) {
|
||||
place.resync();
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package ac.grim.grimac.checks.type;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCloseWindow;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerCloseWindow;
|
||||
import org.jetbrains.annotations.MustBeInvokedByOverriders;
|
||||
|
||||
public class InventoryCheck extends BlockPlaceCheck implements PacketCheck {
|
||||
// Impossible transaction ID
|
||||
protected static final long NONE = Long.MAX_VALUE;
|
||||
|
||||
protected long closeTransaction = NONE;
|
||||
protected int closePacketsToSkip;
|
||||
|
||||
public InventoryCheck(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@MustBeInvokedByOverriders
|
||||
public void onPacketReceive(final PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
|
||||
// Disallow any clicks if inventory is closing
|
||||
if (closeTransaction != NONE && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
player.getInventory().needResend = true;
|
||||
}
|
||||
} else if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
|
||||
// Players with high ping can close inventory faster than send transaction back
|
||||
if (closeTransaction != NONE && closePacketsToSkip-- <= 0) {
|
||||
closeTransaction = NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void closeInventory() {
|
||||
if (closeTransaction != NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
int windowId = player.getInventory().openWindowID;
|
||||
|
||||
player.user.writePacket(new WrapperPlayServerCloseWindow(windowId));
|
||||
|
||||
// Force close inventory on server side
|
||||
closePacketsToSkip = 1; // Sending close packet to itself, so skip it
|
||||
PacketEvents.getAPI().getProtocolManager().receivePacket(
|
||||
player.user.getChannel(), new WrapperPlayClientCloseWindow(windowId)
|
||||
);
|
||||
|
||||
player.sendTransaction();
|
||||
|
||||
int transaction = player.lastTransactionSent.get();
|
||||
closeTransaction = transaction;
|
||||
player.latencyUtils.addRealTimeTask(transaction, () -> {
|
||||
if (closeTransaction == transaction) {
|
||||
closeTransaction = NONE;
|
||||
}
|
||||
});
|
||||
|
||||
player.user.flushPackets();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package ac.grim.grimac.commands;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import co.aikar.commands.BaseCommand;
|
||||
import co.aikar.commands.annotation.*;
|
||||
@@ -70,4 +71,17 @@ public class GrimDebug extends BaseCommand {
|
||||
|
||||
sender.sendMessage("Console output for " + (grimPlayer.bukkitPlayer == null ? grimPlayer.user.getProfile().getName() : grimPlayer.bukkitPlayer.getName()) + " is now " + isOutput);
|
||||
}
|
||||
|
||||
@Subcommand("hitboxdebug")
|
||||
@CommandPermission("grim.hitboxdebug")
|
||||
@CommandCompletion("@players")
|
||||
public void onHitboxDebug(CommandSender sender, @Optional OnlinePlayer target) {
|
||||
Player player = null;
|
||||
if (sender instanceof Player) player = (Player) sender;
|
||||
|
||||
GrimPlayer grimPlayer = parseTarget(sender, player, target);
|
||||
if (grimPlayer == null) return;
|
||||
|
||||
grimPlayer.checkManager.getCheck(HitboxDebugHandler.class).toggleListener(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package ac.grim.grimac.commands;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.manager.violationdatabase.Violation;
|
||||
import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import co.aikar.commands.BaseCommand;
|
||||
import co.aikar.commands.annotation.*;
|
||||
import io.github.retrooper.packetevents.util.folia.FoliaScheduler;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@CommandAlias("grim|grimac")
|
||||
public class GrimHistory extends BaseCommand {
|
||||
|
||||
@Subcommand("history|hist")
|
||||
@CommandPermission("grim.history")
|
||||
@CommandAlias("gh")
|
||||
@CommandCompletion("@players")
|
||||
public void onLogs(CommandSender sender, String target, @Optional Integer page) {
|
||||
int entriesPerPage = GrimAPI.INSTANCE.getConfigManager().getConfig().getIntElse("history.entries-per-page", 15);
|
||||
|
||||
String header = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("grim-history-header",
|
||||
"%prefix% &bShowing logs for &f%player% (&f%page%&b/&f%maxPages%&f)");
|
||||
String logFormat = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("grim-history-entry",
|
||||
"%prefix% &8[&f%server%&8] &bFailed &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% ago&7)");
|
||||
|
||||
FoliaScheduler.getAsyncScheduler().runNow(GrimAPI.INSTANCE.getPlugin(), __ -> {
|
||||
OfflinePlayer targetPlayer = Bukkit.getOfflinePlayer(target);
|
||||
int notNullPage = page == null ? 1 : page;
|
||||
|
||||
ViolationDatabaseManager violations = GrimAPI.INSTANCE.getViolationDatabaseManager();
|
||||
int logCount = violations.getLogCount(targetPlayer.getUniqueId());
|
||||
List<Violation> logs = violations.getViolations(targetPlayer.getUniqueId(), notNullPage, entriesPerPage);
|
||||
|
||||
int maxPages = (int) Math.ceil((float) logCount / entriesPerPage);
|
||||
MessageUtil.sendMessage(sender, MessageUtil.miniMessage(MessageUtil.replacePlaceholders(sender, header
|
||||
.replace("%player%", targetPlayer.getName())
|
||||
.replace("%page%", String.valueOf(notNullPage))
|
||||
.replace("%maxPages%", String.valueOf(maxPages))
|
||||
)));
|
||||
|
||||
for (int i = logs.size() - 1; i >= 0; i--) {
|
||||
Violation log = logs.get(i);
|
||||
MessageUtil.sendMessage(sender, MessageUtil.miniMessage(MessageUtil.replacePlaceholders(sender, logFormat
|
||||
.replace("%player%", targetPlayer.getName())
|
||||
.replace("%check%", log.getCheckName())
|
||||
.replace("%verbose%", log.getVerbose())
|
||||
.replace("%vl%", String.valueOf(log.getVl()))
|
||||
.replace("%timeago%", getTimeAgo(log.getCreatedAt()))
|
||||
.replace("%server%", log.getServerName())
|
||||
)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected String getTimeAgo(Date date) {
|
||||
long durationMillis = new Date().getTime() - date.getTime();
|
||||
|
||||
long days = TimeUnit.MILLISECONDS.toDays(durationMillis);
|
||||
durationMillis -= TimeUnit.DAYS.toMillis(days);
|
||||
|
||||
long hours = TimeUnit.MILLISECONDS.toHours(durationMillis);
|
||||
durationMillis -= TimeUnit.HOURS.toMillis(hours);
|
||||
|
||||
long minutes = TimeUnit.MILLISECONDS.toMinutes(durationMillis);
|
||||
durationMillis -= TimeUnit.MINUTES.toMillis(minutes);
|
||||
|
||||
long seconds = TimeUnit.MILLISECONDS.toSeconds(durationMillis);
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
if (days > 0) result.append(days).append("d ");
|
||||
if (hours > 0) result.append(hours).append("h ");
|
||||
if (minutes > 0) result.append(minutes).append("m ");
|
||||
if (seconds > 0) result.append(seconds).append("s");
|
||||
|
||||
return result.toString().trim();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,8 @@ public class GrimSpectate extends BaseCommand {
|
||||
@CommandPermission("grim.spectate")
|
||||
@CommandCompletion("@players")
|
||||
public void onSpectate(CommandSender sender, @Optional OnlinePlayer target) {
|
||||
if (!(sender instanceof Player player)) return;
|
||||
if (!(sender instanceof Player)) return;
|
||||
Player player = (Player) sender;
|
||||
|
||||
if (target != null && target.getPlayer().getUniqueId().equals(player.getUniqueId())) {
|
||||
String message = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("cannot-run-on-self", "%prefix% &cYou cannot use this command on yourself!");
|
||||
|
||||
@@ -12,17 +12,18 @@ import co.aikar.commands.annotation.Subcommand;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
|
||||
@CommandAlias("grim|grimac")
|
||||
public class GrimStopSpectating extends BaseCommand {
|
||||
public static final CommandCompletionHandler<BukkitCommandCompletionContext> completionHandler = context -> context.getSender().hasPermission("grim.spectate.stophere") ? List.of("here") : List.of();
|
||||
public static final CommandCompletionHandler<BukkitCommandCompletionContext> completionHandler = context -> context.getSender().hasPermission("grim.spectate.stophere") ? Collections.singletonList("here") : Collections.emptyList();
|
||||
|
||||
@Subcommand("stopspectating")
|
||||
@CommandPermission("grim.spectate")
|
||||
@CommandCompletion("@stopspectating")
|
||||
public void onStopSpectate(CommandSender sender, String[] args) {
|
||||
if (!(sender instanceof Player player)) return;
|
||||
if (!(sender instanceof Player)) return;
|
||||
Player player = (Player) sender;
|
||||
String string = args.length > 0 ? args[0] : null;
|
||||
if (GrimAPI.INSTANCE.getSpectateManager().isSpectating(player.getUniqueId())) {
|
||||
boolean teleportBack = string == null || !string.equalsIgnoreCase("here") || !sender.hasPermission("grim.spectate.stophere");
|
||||
|
||||
@@ -16,13 +16,11 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Objects;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@CommandAlias("grim|grimac")
|
||||
@@ -37,8 +35,6 @@ public class GrimVersion extends BaseCommand {
|
||||
private static long lastCheck;
|
||||
private static final AtomicReference<Component> updateMessage = new AtomicReference<>();
|
||||
|
||||
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
|
||||
|
||||
public static void checkForUpdatesAsync(CommandSender sender) {
|
||||
String current = GrimAPI.INSTANCE.getExternalAPI().getGrimVersion();
|
||||
MessageUtil.sendMessage(sender, Component.text()
|
||||
@@ -60,47 +56,78 @@ public class GrimVersion extends BaseCommand {
|
||||
@SuppressWarnings("deprecation")
|
||||
private static void checkForUpdates(CommandSender sender) {
|
||||
String current = GrimAPI.INSTANCE.getExternalAPI().getGrimVersion();
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("https://api.modrinth.com/v2/project/LJNGWSvH/version"))
|
||||
.GET()
|
||||
.header("User-Agent", "GrimAnticheat/Grim/" + GrimAPI.INSTANCE.getExternalAPI().getGrimVersion())
|
||||
.header("Content-Type", "application/json")
|
||||
.timeout(Duration.of(5, ChronoUnit.SECONDS))
|
||||
.build();
|
||||
// Create the URL object
|
||||
URL url = new URL("https://api.modrinth.com/v2/project/LJNGWSvH/version");
|
||||
|
||||
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() != 200) {
|
||||
// Open the connection
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("User-Agent", "GrimAnticheat/Grim/" + GrimAPI.INSTANCE.getExternalAPI().getGrimVersion());
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setConnectTimeout(5000); // Set timeout to 5 seconds
|
||||
connection.setReadTimeout(5000); // Set read timeout to 5 seconds
|
||||
|
||||
// Get the response code
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode != 200) {
|
||||
Component msg = updateMessage.get();
|
||||
MessageUtil.sendMessage(sender, Objects.requireNonNullElseGet(msg, () -> Component.text()
|
||||
.append(Component.text("Failed to check latest version.").color(NamedTextColor.RED))
|
||||
.build()));
|
||||
LogUtil.error("Failed to check latest GrimAC version. Response code: " + response.statusCode());
|
||||
if (msg == null) {
|
||||
msg = Component.text()
|
||||
.append(Component.text("Failed to check latest version.").color(NamedTextColor.RED))
|
||||
.build();
|
||||
}
|
||||
MessageUtil.sendMessage(sender, msg);
|
||||
LogUtil.error("Failed to check latest GrimAC version. Response code: " + responseCode);
|
||||
return;
|
||||
}
|
||||
// Using old JsonParser method, as old versions of Gson don't include the static one
|
||||
JsonObject object = new JsonParser().parse(response.body()).getAsJsonArray().get(0).getAsJsonObject();
|
||||
String latest = object.get("version_number").getAsString();
|
||||
Status status = compareVersions(current, latest);
|
||||
Component msg = switch (status) {
|
||||
case AHEAD ->
|
||||
Component.text("You are using a development version of GrimAC").color(NamedTextColor.LIGHT_PURPLE);
|
||||
case UPDATED ->
|
||||
Component.text("You are using the latest version of GrimAC").color(NamedTextColor.GREEN);
|
||||
case OUTDATED -> Component.text()
|
||||
.append(Component.text("New GrimAC version found!").color(NamedTextColor.AQUA))
|
||||
.append(Component.text(" Version ").color(NamedTextColor.GRAY))
|
||||
.append(Component.text(latest).color(NamedTextColor.GRAY).decorate(TextDecoration.ITALIC))
|
||||
.append(Component.text(" is available to be downloaded here: ").color(NamedTextColor.GRAY))
|
||||
.append(Component.text("https://modrinth.com/plugin/grimac").color(NamedTextColor.GRAY).decorate(TextDecoration.UNDERLINED)
|
||||
.clickEvent(ClickEvent.openUrl("https://modrinth.com/plugin/grimac")))
|
||||
.build();
|
||||
};
|
||||
updateMessage.set(msg);
|
||||
MessageUtil.sendMessage(sender, msg);
|
||||
} catch (Exception ignored) {
|
||||
|
||||
// Read the response body
|
||||
try (InputStream inputStream = connection.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
|
||||
// Parse the response JSON
|
||||
JsonObject object = JsonParser.parseString(response.toString()).getAsJsonArray().get(0).getAsJsonObject();
|
||||
String latest = object.get("version_number").getAsString();
|
||||
|
||||
// Compare versions
|
||||
Status status = compareVersions(current, latest);
|
||||
Component msg = null;
|
||||
switch (status) {
|
||||
case AHEAD:
|
||||
msg = Component.text("You are using a development version of GrimAC").color(NamedTextColor.LIGHT_PURPLE);
|
||||
break;
|
||||
case UPDATED:
|
||||
msg = Component.text("You are using the latest version of GrimAC").color(NamedTextColor.GREEN);
|
||||
break;
|
||||
case OUTDATED:
|
||||
msg = Component.text()
|
||||
.append(Component.text("New GrimAC version found!").color(NamedTextColor.AQUA))
|
||||
.append(Component.text(" Version ").color(NamedTextColor.GRAY))
|
||||
.append(Component.text(latest).color(NamedTextColor.GRAY).decorate(TextDecoration.ITALIC))
|
||||
.append(Component.text(" is available to be downloaded here: ").color(NamedTextColor.GRAY))
|
||||
.append(Component.text("https://modrinth.com/plugin/grimac").color(NamedTextColor.GRAY).decorate(TextDecoration.UNDERLINED)
|
||||
.clickEvent(ClickEvent.openUrl("https://modrinth.com/plugin/grimac")))
|
||||
.build();
|
||||
break;
|
||||
}
|
||||
updateMessage.set(msg);
|
||||
MessageUtil.sendMessage(sender, msg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
MessageUtil.sendMessage(sender, Component.text("Failed to check latest version.").color(NamedTextColor.RED));
|
||||
LogUtil.error("Failed to check latest GrimAC version.", ignored);
|
||||
LogUtil.error("Failed to check latest GrimAC version.", e);
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ac.grim.grimac.events.packets;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.checks.impl.inventory.InventoryB;
|
||||
import ac.grim.grimac.events.packets.patch.ResyncWorldUtil;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.*;
|
||||
@@ -14,7 +15,6 @@ import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.*;
|
||||
import ac.grim.grimac.utils.inventory.Inventory;
|
||||
import ac.grim.grimac.utils.latency.CompensatedWorld;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.math.VectorUtils;
|
||||
import ac.grim.grimac.utils.nmsutil.*;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
@@ -52,88 +52,18 @@ import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static ac.grim.grimac.utils.nmsutil.BlockRayTrace.traverseBlocks;
|
||||
|
||||
public class CheckManagerListener extends PacketListenerAbstract {
|
||||
|
||||
public CheckManagerListener() {
|
||||
super(PacketListenerPriority.LOW);
|
||||
}
|
||||
|
||||
// Copied from MCP...
|
||||
// Returns null if there isn't anything.
|
||||
//
|
||||
// I do have to admit that I'm starting to like bifunctions/new java 8 things more than I originally did.
|
||||
// although I still don't understand Mojang's obsession with streams in some of the hottest methods... that kills performance
|
||||
public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d end, BiFunction<WrappedBlockState, Vector3i, HitData> predicate) {
|
||||
// I guess go back by the collision epsilon?
|
||||
double endX = GrimMath.lerp(-1.0E-7D, end.x, start.x);
|
||||
double endY = GrimMath.lerp(-1.0E-7D, end.y, start.y);
|
||||
double endZ = GrimMath.lerp(-1.0E-7D, end.z, start.z);
|
||||
double startX = GrimMath.lerp(-1.0E-7D, start.x, end.x);
|
||||
double startY = GrimMath.lerp(-1.0E-7D, start.y, end.y);
|
||||
double startZ = GrimMath.lerp(-1.0E-7D, start.z, end.z);
|
||||
int floorStartX = GrimMath.floor(startX);
|
||||
int floorStartY = GrimMath.floor(startY);
|
||||
int floorStartZ = GrimMath.floor(startZ);
|
||||
|
||||
|
||||
if (start.equals(end)) return null;
|
||||
|
||||
WrappedBlockState state = player.compensatedWorld.getBlock(floorStartX, floorStartY, floorStartZ);
|
||||
HitData apply = predicate.apply(state, new Vector3i(floorStartX, floorStartY, floorStartZ));
|
||||
|
||||
if (apply != null) {
|
||||
return apply;
|
||||
}
|
||||
|
||||
double xDiff = endX - startX;
|
||||
double yDiff = endY - startY;
|
||||
double zDiff = endZ - startZ;
|
||||
double xSign = Math.signum(xDiff);
|
||||
double ySign = Math.signum(yDiff);
|
||||
double zSign = Math.signum(zDiff);
|
||||
|
||||
double posXInverse = xSign == 0 ? Double.MAX_VALUE : xSign / xDiff;
|
||||
double posYInverse = ySign == 0 ? Double.MAX_VALUE : ySign / yDiff;
|
||||
double posZInverse = zSign == 0 ? Double.MAX_VALUE : zSign / zDiff;
|
||||
|
||||
double d12 = posXInverse * (xSign > 0 ? 1.0D - GrimMath.frac(startX) : GrimMath.frac(startX));
|
||||
double d13 = posYInverse * (ySign > 0 ? 1.0D - GrimMath.frac(startY) : GrimMath.frac(startY));
|
||||
double d14 = posZInverse * (zSign > 0 ? 1.0D - GrimMath.frac(startZ) : GrimMath.frac(startZ));
|
||||
|
||||
// Can't figure out what this code does currently
|
||||
while (d12 <= 1.0D || d13 <= 1.0D || d14 <= 1.0D) {
|
||||
if (d12 < d13) {
|
||||
if (d12 < d14) {
|
||||
floorStartX += xSign;
|
||||
d12 += posXInverse;
|
||||
} else {
|
||||
floorStartZ += zSign;
|
||||
d14 += posZInverse;
|
||||
}
|
||||
} else if (d13 < d14) {
|
||||
floorStartY += ySign;
|
||||
d13 += posYInverse;
|
||||
} else {
|
||||
floorStartZ += zSign;
|
||||
d14 += posZInverse;
|
||||
}
|
||||
|
||||
state = player.compensatedWorld.getBlock(floorStartX, floorStartY, floorStartZ);
|
||||
apply = predicate.apply(state, new Vector3i(floorStartX, floorStartY, floorStartZ));
|
||||
|
||||
if (apply != null) {
|
||||
return apply;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void placeWaterLavaSnowBucket(GrimPlayer player, ItemStack held, StateType toPlace, InteractionHand hand) {
|
||||
HitData data = getNearestHitResult(player, StateTypes.AIR, false, true, true);
|
||||
BlockHitData data = getNearestHitResult(player, StateTypes.AIR, false, true, true);
|
||||
if (data != null) {
|
||||
BlockPlace blockPlace = new BlockPlace(player, hand, data.getPosition(), data.getClosestDirection().getFaceValue(), data.getClosestDirection(), held, data);
|
||||
|
||||
@@ -262,8 +192,9 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
|
||||
private static void handleBlockPlaceOrUseItem(PacketWrapper<?> packet, GrimPlayer player) {
|
||||
// Legacy "use item" packet
|
||||
if (packet instanceof WrapperPlayClientPlayerBlockPlacement place &&
|
||||
if (packet instanceof WrapperPlayClientPlayerBlockPlacement &&
|
||||
PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_9)) {
|
||||
WrapperPlayClientPlayerBlockPlacement place = (WrapperPlayClientPlayerBlockPlacement) packet;
|
||||
|
||||
if (player.gamemode == GameMode.SPECTATOR || player.gamemode == GameMode.ADVENTURE) return;
|
||||
|
||||
@@ -278,7 +209,9 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
if (packet instanceof WrapperPlayClientUseItem place) {
|
||||
if (packet instanceof WrapperPlayClientUseItem) {
|
||||
WrapperPlayClientUseItem place = (WrapperPlayClientUseItem) packet;
|
||||
|
||||
if (player.gamemode == GameMode.SPECTATOR || player.gamemode == GameMode.ADVENTURE) return;
|
||||
|
||||
ItemStack placedWith = player.getInventory().getHeldItem();
|
||||
@@ -290,7 +223,9 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
}
|
||||
|
||||
// Check for interactable first (door, etc)
|
||||
if (packet instanceof WrapperPlayClientPlayerBlockPlacement place) {
|
||||
if (packet instanceof WrapperPlayClientPlayerBlockPlacement) {
|
||||
WrapperPlayClientPlayerBlockPlacement place = (WrapperPlayClientPlayerBlockPlacement) packet;
|
||||
|
||||
ItemStack placedWith = player.getInventory().getHeldItem();
|
||||
ItemStack offhand = player.getInventory().getOffHand();
|
||||
|
||||
@@ -327,7 +262,8 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
if (packet instanceof WrapperPlayClientPlayerBlockPlacement place) {
|
||||
if (packet instanceof WrapperPlayClientPlayerBlockPlacement) {
|
||||
WrapperPlayClientPlayerBlockPlacement place = (WrapperPlayClientPlayerBlockPlacement) packet;
|
||||
if (player.gamemode == GameMode.SPECTATOR || player.gamemode == GameMode.ADVENTURE) return;
|
||||
|
||||
Vector3i blockPosition = place.getBlockPosition();
|
||||
@@ -499,6 +435,8 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
final WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
|
||||
final DiggingAction action = packet.getAction();
|
||||
|
||||
player.checkManager.getPacketCheck(InventoryB.class).handle(event, packet);
|
||||
|
||||
if (action == DiggingAction.START_DIGGING || action == DiggingAction.FINISHED_DIGGING || action == DiggingAction.CANCELLED_DIGGING) {
|
||||
final BlockBreak blockBreak = new BlockBreak(player, packet.getBlockPosition(), packet.getBlockFace(), packet.getBlockFaceId(), action, player.compensatedWorld.getBlock(packet.getBlockPosition()));
|
||||
|
||||
@@ -513,6 +451,15 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
|
||||
if (action == DiggingAction.FINISHED_DIGGING && BREAKABLE.apply(blockBreak.block.getType())) {
|
||||
player.compensatedWorld.startPredicting();
|
||||
player.blockHistory.add(
|
||||
new BlockModification(
|
||||
player.compensatedWorld.getBlock(blockBreak.position),
|
||||
WrappedBlockState.getByGlobalId(0),
|
||||
blockBreak.position,
|
||||
GrimAPI.INSTANCE.getTickManager().currentTick,
|
||||
BlockModification.Cause.FINISHED_DIGGING
|
||||
)
|
||||
);
|
||||
player.compensatedWorld.updateBlock(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0);
|
||||
player.compensatedWorld.stopPredicting(packet);
|
||||
}
|
||||
@@ -645,7 +592,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
}
|
||||
|
||||
private static void placeBucket(GrimPlayer player, InteractionHand hand) {
|
||||
HitData data = getNearestHitResult(player, null, true, false, true);
|
||||
BlockHitData data = getNearestHitResult(player, null, true, false, true);
|
||||
|
||||
if (data != null) {
|
||||
BlockPlace blockPlace = new BlockPlace(player, hand, data.getPosition(), data.getClosestDirection().getFaceValue(), data.getClosestDirection(), ItemStack.EMPTY, data);
|
||||
@@ -818,7 +765,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
}
|
||||
|
||||
private static void placeLilypad(GrimPlayer player, InteractionHand hand) {
|
||||
HitData data = getNearestHitResult(player, null, true, false, true);
|
||||
BlockHitData data = getNearestHitResult(player, null, true, false, true);
|
||||
|
||||
if (data != null) {
|
||||
// A lilypad cannot replace a fluid
|
||||
@@ -848,7 +795,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
private static HitData getNearestHitResult(GrimPlayer player, StateType heldItem, boolean sourcesHaveHitbox, boolean fluidPlacement, boolean itemUsePlacement) {
|
||||
private static BlockHitData getNearestHitResult(GrimPlayer player, StateType heldItem, boolean sourcesHaveHitbox, boolean fluidPlacement, boolean itemUsePlacement) {
|
||||
Vector3d startingPos = new Vector3d(player.x, player.y + player.getEyeHeight(), player.z);
|
||||
Vector startingVec = new Vector(startingPos.getX(), startingPos.getY(), startingPos.getZ());
|
||||
Ray trace = new Ray(player, startingPos.getX(), startingPos.getY(), startingPos.getZ(), player.xRot, player.yRot);
|
||||
@@ -856,7 +803,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
Vector endVec = trace.getPointAtDistance(distance);
|
||||
Vector3d endPos = new Vector3d(endVec.getX(), endVec.getY(), endVec.getZ());
|
||||
|
||||
return traverseBlocks(player, startingPos, endPos, (block, vector3i) -> {
|
||||
return (BlockHitData) traverseBlocks(player, startingPos, endPos, (block, vector3i) -> {
|
||||
if (fluidPlacement && player.getClientVersion().isOlderThan(ClientVersion.V_1_13) && CollisionData.getData(block.getType())
|
||||
.getMovementCollisionBox(player, player.getClientVersion(), block, vector3i.getX(), vector3i.getY(), vector3i.getZ()).isNull()) {
|
||||
return null;
|
||||
@@ -883,7 +830,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
}
|
||||
}
|
||||
if (bestHitLoc != null) {
|
||||
return new HitData(vector3i, bestHitLoc, bestFace, block);
|
||||
return new BlockHitData(vector3i, bestHitLoc, bestFace, block, true);
|
||||
}
|
||||
|
||||
if (sourcesHaveHitbox &&
|
||||
@@ -896,7 +843,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
Pair<Vector, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
|
||||
|
||||
if (intercept.first() != null) {
|
||||
return new HitData(vector3i, intercept.first(), intercept.second(), block);
|
||||
return new BlockHitData(vector3i, intercept.first(), intercept.second(), block, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -237,8 +237,9 @@ public class PacketEntityReplication extends Check implements PacketCheck {
|
||||
|
||||
if (status.getStatus() == 31) {
|
||||
PacketEntity hook = player.compensatedEntities.getEntity(status.getEntityId());
|
||||
if (!(hook instanceof PacketEntityHook hookEntity)) return;
|
||||
if (!(hook instanceof PacketEntityHook)) return;
|
||||
|
||||
PacketEntityHook hookEntity = (PacketEntityHook) hook;
|
||||
if (hookEntity.attached == player.entityID) {
|
||||
player.sendTransaction();
|
||||
// We don't transaction sandwich this, it's too rare to be a real problem.
|
||||
@@ -256,7 +257,7 @@ public class PacketEntityReplication extends Check implements PacketCheck {
|
||||
|
||||
if (slot.getWindowId() == 0) {
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> {
|
||||
if (slot.getSlot() - 36 == player.packetStateData.lastSlotSelected) {
|
||||
if (slot.getSlot() - 36 == player.packetStateData.lastSlotSelected && (player.getInventory().getHeldItem().getType() == slot.getItem().getType() || player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8))) {
|
||||
player.packetStateData.setSlowedByUsingItem(false);
|
||||
|
||||
if (player.isResetItemUsageOnItemUpdate()) {
|
||||
@@ -266,7 +267,7 @@ public class PacketEntityReplication extends Check implements PacketCheck {
|
||||
});
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get() + 1, () -> {
|
||||
if (slot.getSlot() - 36 == player.packetStateData.lastSlotSelected) {
|
||||
if (slot.getSlot() - 36 == player.packetStateData.lastSlotSelected && (player.getInventory().getHeldItem().getType() == slot.getItem().getType() || player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8))) {
|
||||
player.packetStateData.setSlowedByUsingItem(false);
|
||||
|
||||
if (player.isResetItemUsageOnItemUpdate()) {
|
||||
@@ -375,7 +376,7 @@ public class PacketEntityReplication extends Check implements PacketCheck {
|
||||
}
|
||||
|
||||
private void handleMountVehicle(PacketSendEvent event, int vehicleID, int[] passengers) {
|
||||
boolean wasInVehicle = player.getRidingVehicleId() == vehicleID;
|
||||
boolean wasInVehicle = player.compensatedEntities.serverPlayerVehicle != null && player.compensatedEntities.serverPlayerVehicle == vehicleID;
|
||||
boolean inThisVehicle = false;
|
||||
|
||||
for (int passenger : passengers) {
|
||||
@@ -447,6 +448,7 @@ public class PacketEntityReplication extends Check implements PacketCheck {
|
||||
return;
|
||||
}
|
||||
|
||||
// player.compensatedEntities.entityMap.updateEntityPosition(player.compensatedEntities.entityMap.get(entityId), new Vector3d(data.getX() + deltaX, data.getY() + deltaY, data.getZ() + deltaZ));
|
||||
data.setX(data.getX() + deltaX);
|
||||
data.setY(data.getY() + deltaY);
|
||||
data.setZ(data.getZ() + deltaZ);
|
||||
@@ -472,7 +474,8 @@ public class PacketEntityReplication extends Check implements PacketCheck {
|
||||
player.latencyUtils.addRealTimeTask(lastTrans, () -> {
|
||||
PacketEntity entity = player.compensatedEntities.getEntity(entityId);
|
||||
if (entity == null) return;
|
||||
if (entity instanceof PacketEntityTrackXRot xRotEntity && yaw != null) {
|
||||
if (entity instanceof PacketEntityTrackXRot && yaw != null) {
|
||||
PacketEntityTrackXRot xRotEntity = (PacketEntityTrackXRot) entity;
|
||||
xRotEntity.packetYaw = yaw;
|
||||
xRotEntity.steps = entity.isBoat() ? 10 : 3;
|
||||
}
|
||||
@@ -487,7 +490,7 @@ public class PacketEntityReplication extends Check implements PacketCheck {
|
||||
});
|
||||
}
|
||||
|
||||
public void addEntity(int entityID, UUID uuid, EntityType type, Vector3d position, float xRot, float yRot, List<EntityData> entityMetadata, int extraData) {
|
||||
public void addEntity(int entityID, UUID uuid, EntityType type, Vector3d position, float xRot, float yRot, List<EntityData<?>> entityMetadata, int extraData) {
|
||||
if (despawnedEntitiesThisTransaction.contains(entityID)) {
|
||||
player.sendTransaction();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.protocol.component.ComponentTypes;
|
||||
import com.github.retrooper.packetevents.protocol.component.builtin.item.FoodProperties;
|
||||
import com.github.retrooper.packetevents.protocol.component.builtin.item.ItemConsumable;
|
||||
import com.github.retrooper.packetevents.protocol.item.ItemStack;
|
||||
import com.github.retrooper.packetevents.protocol.item.enchantment.type.EnchantmentTypes;
|
||||
import com.github.retrooper.packetevents.protocol.item.type.ItemType;
|
||||
@@ -43,8 +44,17 @@ public class PacketPlayerDigging extends PacketListenerAbstract {
|
||||
|
||||
final ItemType material = item.getType();
|
||||
|
||||
// Check for data component stuff on 1.20.5+
|
||||
// Check for data component stuff on 1.21.2+
|
||||
final ItemConsumable consumable = item.getComponentOr(ComponentTypes.CONSUMABLE, null);
|
||||
final FoodProperties foodComponent = item.getComponentOr(ComponentTypes.FOOD, null);
|
||||
|
||||
// The food component can override the consumable component, as it provides conditions for using the item
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2) && consumable != null && foodComponent == null) {
|
||||
player.packetStateData.setSlowedByUsingItem(true);
|
||||
player.packetStateData.eatingHand = hand;
|
||||
}
|
||||
|
||||
// Check for data component stuff on 1.20.5+
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5) && foodComponent != null) {
|
||||
if (foodComponent.isCanAlwaysEat() || player.food < 20 || player.gamemode == GameMode.CREATIVE) {
|
||||
player.packetStateData.setSlowedByUsingItem(true);
|
||||
@@ -114,17 +124,15 @@ public class PacketPlayerDigging extends PacketListenerAbstract {
|
||||
// Players in survival can't use a bow without an arrow
|
||||
// Crossbow charge checked previously
|
||||
if (material == ItemTypes.BOW || material == ItemTypes.CROSSBOW) {
|
||||
/*player.packetStateData.slowedByUsingItem = player.gamemode == GameMode.CREATIVE ||
|
||||
player.getInventory().hasItemType(ItemTypes.ARROW) ||
|
||||
player.getInventory().hasItemType(ItemTypes.TIPPED_ARROW) ||
|
||||
player.getInventory().hasItemType(ItemTypes.SPECTRAL_ARROW);
|
||||
player.packetStateData.eatingHand = place.getHand();*/
|
||||
boolean isSlowedByUsingItem = player.gamemode == GameMode.CREATIVE ||
|
||||
player.getInventory().hasAnyOfItemType(ItemTypes.ARROW, ItemTypes.TIPPED_ARROW, ItemTypes.SPECTRAL_ARROW);
|
||||
player.packetStateData.eatingHand = hand;
|
||||
// TODO: How do we lag compensate arrows? Mojang removed idle packet.
|
||||
// I think we may have to cancel the bukkit event if the player isn't slowed
|
||||
// On 1.8, it wouldn't be too bad to handle bows correctly
|
||||
// But on 1.9+, no idle packet and clients/servers don't agree on bow status
|
||||
// Mojang pls fix
|
||||
player.packetStateData.setSlowedByUsingItem(false);
|
||||
player.packetStateData.setSlowedByUsingItem(isSlowedByUsingItem);
|
||||
}
|
||||
|
||||
if (material == ItemTypes.SPYGLASS && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_17)) {
|
||||
@@ -226,6 +234,8 @@ public class PacketPlayerDigging extends PacketListenerAbstract {
|
||||
final ItemStack item = hand == InteractionHand.MAIN_HAND ?
|
||||
player.getInventory().getHeldItem() : player.getInventory().getOffHand();
|
||||
|
||||
final boolean wasSlow = player.packetStateData.isSlowedByUsingItem();
|
||||
|
||||
handleUseItem(player, item, hand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package ac.grim.grimac.events.packets;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntitySelf;
|
||||
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
||||
import com.github.retrooper.packetevents.event.PacketListenerPriority;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus.Action;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow;
|
||||
|
||||
public class PacketPlayerWindow extends PacketListenerAbstract {
|
||||
|
||||
public PacketPlayerWindow() {
|
||||
super(PacketListenerPriority.LOWEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && !event.isCancelled()) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
if (player.hasInventoryOpen && isNearNetherPortal(player)) {
|
||||
handleInventoryClose(player, InventoryDesyncStatus.NETHER_PORTAL);
|
||||
}
|
||||
}
|
||||
|
||||
// Client Status is sent in 1.7-1.8
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS) {
|
||||
WrapperPlayClientClientStatus wrapper = new WrapperPlayClientClientStatus(event);
|
||||
|
||||
if (wrapper.getAction() == Action.OPEN_INVENTORY_ACHIEVEMENT) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
handleInventoryOpen(player);
|
||||
}
|
||||
}
|
||||
|
||||
// We need to do this due to 1.9 not sending anymore the inventory action in the Client Status
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
// TODO: Remove this check after we finish the before ViaVersion injection
|
||||
// Explanation: On 1.7 and 1.8 we have OPEN_INVENTORY_ACHIEVEMENT on CLIENT_STATUS packet
|
||||
// but after Via translation this information gets lost
|
||||
// This is a workaround to atleast make our inventory checks work "decently" in 1.8 clients for 1.9+ servers
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)
|
||||
&& player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
|
||||
handleInventoryOpen(player);
|
||||
}
|
||||
|
||||
if (player.getClientVersion().isNewerThan(ClientVersion.V_1_8)) {
|
||||
handleInventoryOpen(player);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(PacketSendEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Server.RESPAWN) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
player.sendTransaction();
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
|
||||
() -> handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED));
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) {
|
||||
WrapperPlayServerOpenWindow wrapper = new WrapperPlayServerOpenWindow(event);
|
||||
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
player.sendTransaction();
|
||||
|
||||
String legacyType = wrapper.getLegacyType();
|
||||
int modernType = wrapper.getType();
|
||||
InventoryDesyncStatus inventoryDesyncStatus = getContainerDesyncStatus(player, legacyType, modernType);
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
|
||||
() -> {
|
||||
if (inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) {
|
||||
handleInventoryOpen(player);
|
||||
} else {
|
||||
handleInventoryClose(player, inventoryDesyncStatus);
|
||||
}
|
||||
});
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.OPEN_HORSE_WINDOW) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
player.sendTransaction();
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
|
||||
() -> handleInventoryOpen(player));
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.CLOSE_WINDOW) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
player.sendTransaction();
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
|
||||
() -> handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInventoryOpen(GrimPlayer player) {
|
||||
if (!player.hasInventoryOpen) {
|
||||
player.lastInventoryOpen = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
player.hasInventoryOpen = true;
|
||||
}
|
||||
|
||||
private void handleInventoryClose(GrimPlayer player, InventoryDesyncStatus desyncStatus) {
|
||||
player.hasInventoryOpen = false;
|
||||
player.inventoryDesyncStatus = desyncStatus;
|
||||
}
|
||||
|
||||
public InventoryDesyncStatus getContainerDesyncStatus(GrimPlayer player, String legacyType, int modernType) {
|
||||
// Closing beacon with the cross button cause desync in 1.7-1.8
|
||||
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) &&
|
||||
("minecraft:beacon".equals(legacyType) || modernType == 8)) {
|
||||
return player.inventoryDesyncStatus = InventoryDesyncStatus.BEACON;
|
||||
}
|
||||
|
||||
if (isNearNetherPortal(player)) {
|
||||
return player.inventoryDesyncStatus = InventoryDesyncStatus.NETHER_PORTAL;
|
||||
}
|
||||
|
||||
return player.inventoryDesyncStatus = InventoryDesyncStatus.NOT_DESYNCED;
|
||||
}
|
||||
|
||||
public boolean isNearNetherPortal(GrimPlayer player) {
|
||||
// Going inside nether portal with opened inventory cause desync, fixed in 1.12.2
|
||||
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_12_1) &&
|
||||
player.pointThreeEstimator.isNearNetherPortal) {
|
||||
PacketEntitySelf playerEntity = player.compensatedEntities.self;
|
||||
// Client ignore nether portal if player has passengers or riding an entity
|
||||
return !playerEntity.inVehicle() && playerEntity.passengers.isEmpty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class PacketSelfMetadataListener extends PacketListenerAbstract {
|
||||
//
|
||||
// It makes no sense to me why mojang is doing this, it has to be a bug.
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_14)) {
|
||||
List<EntityData> metadataStuff = entityMetadata.getEntityMetadata();
|
||||
List<EntityData<?>> metadataStuff = entityMetadata.getEntityMetadata();
|
||||
|
||||
// Remove the pose metadata from the list
|
||||
metadataStuff.removeIf(element -> element.getIndex() == 6);
|
||||
@@ -71,7 +71,7 @@ public class PacketSelfMetadataListener extends PacketListenerAbstract {
|
||||
event.markForReEncode(true);
|
||||
}
|
||||
|
||||
EntityData watchable = WatchableIndexUtil.getIndex(entityMetadata.getEntityMetadata(), 0);
|
||||
EntityData<?> watchable = WatchableIndexUtil.getIndex(entityMetadata.getEntityMetadata(), 0);
|
||||
|
||||
if (watchable != null) {
|
||||
Object zeroBitField = watchable.getValue();
|
||||
@@ -98,7 +98,7 @@ public class PacketSelfMetadataListener extends PacketListenerAbstract {
|
||||
}
|
||||
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)) {
|
||||
EntityData gravity = WatchableIndexUtil.getIndex(entityMetadata.getEntityMetadata(), 5);
|
||||
EntityData<?> gravity = WatchableIndexUtil.getIndex(entityMetadata.getEntityMetadata(), 5);
|
||||
|
||||
if (gravity != null) {
|
||||
Object gravityObject = gravity.getValue();
|
||||
@@ -117,7 +117,7 @@ public class PacketSelfMetadataListener extends PacketListenerAbstract {
|
||||
}
|
||||
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17)) {
|
||||
EntityData frozen = WatchableIndexUtil.getIndex(entityMetadata.getEntityMetadata(), 7);
|
||||
EntityData<?> frozen = WatchableIndexUtil.getIndex(entityMetadata.getEntityMetadata(), 7);
|
||||
|
||||
if (frozen != null) {
|
||||
if (!hasSendTransaction) player.sendTransaction();
|
||||
@@ -139,7 +139,7 @@ public class PacketSelfMetadataListener extends PacketListenerAbstract {
|
||||
id = 14; // 1.17 changed this to 14
|
||||
}
|
||||
|
||||
EntityData bedObject = WatchableIndexUtil.getIndex(entityMetadata.getEntityMetadata(), id);
|
||||
EntityData<?> bedObject = WatchableIndexUtil.getIndex(entityMetadata.getEntityMetadata(), id);
|
||||
if (bedObject != null) {
|
||||
if (!hasSendTransaction) player.sendTransaction();
|
||||
hasSendTransaction = true;
|
||||
@@ -159,7 +159,7 @@ public class PacketSelfMetadataListener extends PacketListenerAbstract {
|
||||
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13) &&
|
||||
player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
EntityData riptide = WatchableIndexUtil.getIndex(entityMetadata.getEntityMetadata(), PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17) ? 8 : 7);
|
||||
EntityData<?> riptide = WatchableIndexUtil.getIndex(entityMetadata.getEntityMetadata(), PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17) ? 8 : 7);
|
||||
|
||||
// This one only present if it changed
|
||||
if (riptide != null && riptide.getValue() instanceof Byte) {
|
||||
|
||||
@@ -92,7 +92,7 @@ public class PacketServerTeleport extends PacketListenerAbstract {
|
||||
pos = pos.withY(pos.getY() - 1.62);
|
||||
|
||||
Location target = new Location(null, pos.getX(), pos.getY(), pos.getZ());
|
||||
player.getSetbackTeleportUtil().addSentTeleport(target, lastTransactionSent, teleport.getRelativeFlags(), true, teleport.getTeleportId());
|
||||
player.getSetbackTeleportUtil().addSentTeleport(target, teleport.getDeltaMovement(), lastTransactionSent, teleport.getRelativeFlags(), true, teleport.getTeleportId());
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Server.VEHICLE_MOVE) {
|
||||
|
||||
@@ -162,7 +162,7 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
|
||||
|
||||
int range = 16;
|
||||
|
||||
final var blocks = multiBlockChange.getBlocks();
|
||||
final WrapperPlayServerMultiBlockChange.EncodedBlock[] blocks = multiBlockChange.getBlocks();
|
||||
for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) {
|
||||
// Don't send a transaction unless it's within 16 blocks of the player
|
||||
if (Math.abs(blockChange.getX() - player.x) < range && Math.abs(blockChange.getY() - player.y) < range && Math.abs(blockChange.getZ() - player.z) < range && player.lastTransSent + 2 < System.currentTimeMillis()) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.BitSet;
|
||||
public class PacketWorldReaderEight extends BasePacketWorldReader {
|
||||
@Override
|
||||
public void handleMapChunkBulk(final GrimPlayer player, final PacketSendEvent event) {
|
||||
PacketWrapper<?> wrapper = new PacketWrapper<>(event);
|
||||
PacketWrapper wrapper = new PacketWrapper(event);
|
||||
ByteBuf buffer = (ByteBuf) wrapper.getBuffer();
|
||||
|
||||
boolean skylight = wrapper.readBoolean();
|
||||
@@ -47,7 +47,7 @@ public class PacketWorldReaderEight extends BasePacketWorldReader {
|
||||
|
||||
@Override
|
||||
public void handleMapChunk(final GrimPlayer player, final PacketSendEvent event) {
|
||||
PacketWrapper<?> wrapper = new PacketWrapper<>(event);
|
||||
PacketWrapper wrapper = new PacketWrapper(event);
|
||||
|
||||
final int chunkX = wrapper.readInt();
|
||||
final int chunkZ = wrapper.readInt();
|
||||
|
||||
+7
-7
@@ -2,15 +2,12 @@ package ac.grim.grimac.events.packets.worldreader;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.protocol.stream.NetStreamInput;
|
||||
import com.github.retrooper.packetevents.protocol.world.chunk.BaseChunk;
|
||||
import com.github.retrooper.packetevents.protocol.world.chunk.impl.v_1_18.Chunk_v1_18;
|
||||
import com.github.retrooper.packetevents.protocol.world.chunk.reader.impl.ChunkReader_v1_18;
|
||||
import com.github.retrooper.packetevents.protocol.world.dimension.DimensionTypes;
|
||||
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
public class PacketWorldReaderEighteen extends BasePacketWorldReader {
|
||||
|
||||
// Mojang decided to include lighting in this packet. It's inefficient to read it, so we replace PacketEvents logic.
|
||||
@@ -24,10 +21,13 @@ public class PacketWorldReaderEighteen extends BasePacketWorldReader {
|
||||
// Skip past heightmaps
|
||||
wrapper.readNBT();
|
||||
|
||||
BaseChunk[] chunks = new ChunkReader_v1_18().read(DimensionTypes.OVERWORLD,null,
|
||||
null, true, false, false,
|
||||
event.getUser().getTotalWorldHeight() >> 4, null,
|
||||
new NetStreamInput(new ByteArrayInputStream(wrapper.readByteArray())));
|
||||
BaseChunk[] chunks = new ChunkReader_v1_18().read(
|
||||
DimensionTypes.OVERWORLD,
|
||||
null, null, true, false, false,
|
||||
event.getUser().getTotalWorldHeight() >> 4,
|
||||
wrapper.readVarInt(),
|
||||
wrapper
|
||||
);
|
||||
|
||||
// Remove biomes to save memory
|
||||
for (int i = 0; i < chunks.length; i++) {
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
package ac.grim.grimac.manager;
|
||||
|
||||
import ac.grim.grimac.api.AbstractCheck;
|
||||
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
|
||||
import ac.grim.grimac.checks.impl.aim.AimDuplicateLook;
|
||||
import ac.grim.grimac.checks.impl.aim.AimModulo360;
|
||||
import ac.grim.grimac.checks.impl.aim.processor.AimProcessor;
|
||||
import ac.grim.grimac.checks.impl.badpackets.*;
|
||||
import ac.grim.grimac.checks.impl.breaking.*;
|
||||
import ac.grim.grimac.checks.impl.combat.Hitboxes;
|
||||
import ac.grim.grimac.checks.impl.combat.MultiInteractA;
|
||||
import ac.grim.grimac.checks.impl.combat.MultiInteractB;
|
||||
import ac.grim.grimac.checks.impl.combat.Reach;
|
||||
import ac.grim.grimac.checks.impl.combat.*;
|
||||
import ac.grim.grimac.checks.impl.crash.*;
|
||||
import ac.grim.grimac.checks.impl.elytra.*;
|
||||
import ac.grim.grimac.checks.impl.exploit.ExploitA;
|
||||
import ac.grim.grimac.checks.impl.exploit.ExploitB;
|
||||
import ac.grim.grimac.checks.impl.exploit.ExploitC;
|
||||
import ac.grim.grimac.checks.impl.groundspoof.NoFall;
|
||||
import ac.grim.grimac.checks.impl.inventory.*;
|
||||
import ac.grim.grimac.checks.impl.misc.ClientBrand;
|
||||
import ac.grim.grimac.checks.impl.misc.GhostBlockMitigation;
|
||||
import ac.grim.grimac.checks.impl.misc.TransactionOrder;
|
||||
import ac.grim.grimac.checks.impl.movement.*;
|
||||
import ac.grim.grimac.checks.impl.multiactions.*;
|
||||
import ac.grim.grimac.checks.impl.packetorder.*;
|
||||
import ac.grim.grimac.checks.impl.post.Post;
|
||||
import ac.grim.grimac.checks.impl.prediction.DebugHandler;
|
||||
import ac.grim.grimac.checks.impl.prediction.GroundSpoof;
|
||||
@@ -80,8 +80,11 @@ public class CheckManager {
|
||||
public CheckManager(GrimPlayer player) {
|
||||
// Include post checks in the packet check too
|
||||
packetChecks = new ImmutableClassToInstanceMap.Builder<PacketCheck>()
|
||||
.put(Hitboxes.class, new Hitboxes(player))
|
||||
.put(PacketOrderProcessor.class, player.packetOrderProcessor)
|
||||
.put(Reach.class, new Reach(player))
|
||||
.put(HitboxMiss.class, new HitboxMiss(player))
|
||||
.put(HitboxBlock.class, new HitboxBlock(player))
|
||||
.put(HitboxEntity.class, new HitboxEntity(player))
|
||||
.put(PacketEntityReplication.class, new PacketEntityReplication(player))
|
||||
.put(PacketChangeGameState.class, new PacketChangeGameState(player))
|
||||
.put(CompensatedInventory.class, new CompensatedInventory(player))
|
||||
@@ -102,7 +105,6 @@ public class CheckManager {
|
||||
.put(BadPacketsI.class, new BadPacketsI(player))
|
||||
.put(BadPacketsK.class, new BadPacketsK(player))
|
||||
.put(BadPacketsL.class, new BadPacketsL(player))
|
||||
.put(BadPacketsM.class, new BadPacketsM(player))
|
||||
.put(BadPacketsN.class, new BadPacketsN(player))
|
||||
.put(BadPacketsP.class, new BadPacketsP(player))
|
||||
.put(BadPacketsQ.class, new BadPacketsQ(player))
|
||||
@@ -113,11 +115,19 @@ public class CheckManager {
|
||||
.put(BadPacketsV.class, new BadPacketsV(player))
|
||||
.put(BadPacketsW.class, new BadPacketsW(player))
|
||||
.put(BadPacketsY.class, new BadPacketsY(player))
|
||||
.put(InventoryA.class, new InventoryA(player))
|
||||
.put(InventoryB.class, new InventoryB(player))
|
||||
.put(InventoryE.class, new InventoryE(player))
|
||||
.put(InventoryF.class, new InventoryF(player))
|
||||
.put(InventoryG.class, new InventoryG(player))
|
||||
.put(MultiActionsA.class, new MultiActionsA(player))
|
||||
.put(MultiActionsB.class, new MultiActionsB(player))
|
||||
.put(MultiActionsC.class, new MultiActionsC(player))
|
||||
.put(MultiActionsD.class, new MultiActionsD(player))
|
||||
.put(MultiActionsE.class, new MultiActionsE(player))
|
||||
.put(PacketOrderC.class, new PacketOrderC(player))
|
||||
.put(PacketOrderD.class, new PacketOrderD(player))
|
||||
.put(PacketOrderO.class, new PacketOrderO(player))
|
||||
.put(TransactionOrder.class, new TransactionOrder(player))
|
||||
.put(SprintA.class, new SprintA(player))
|
||||
.put(VehicleA.class, new VehicleA(player))
|
||||
@@ -145,8 +155,19 @@ public class CheckManager {
|
||||
.put(ExplosionHandler.class, new ExplosionHandler(player))
|
||||
.put(KnockbackHandler.class, new KnockbackHandler(player))
|
||||
.put(GhostBlockDetector.class, new GhostBlockDetector(player))
|
||||
.put(InventoryD.class, new InventoryD(player))
|
||||
.put(Phase.class, new Phase(player))
|
||||
.put(Post.class, new Post(player))
|
||||
.put(PacketOrderA.class, new PacketOrderA(player))
|
||||
.put(PacketOrderE.class, new PacketOrderE(player))
|
||||
.put(PacketOrderF.class, new PacketOrderF(player))
|
||||
.put(PacketOrderG.class, new PacketOrderG(player))
|
||||
.put(PacketOrderH.class, new PacketOrderH(player))
|
||||
.put(PacketOrderI.class, new PacketOrderI(player))
|
||||
.put(PacketOrderJ.class, new PacketOrderJ(player))
|
||||
.put(PacketOrderK.class, new PacketOrderK(player))
|
||||
.put(PacketOrderL.class, new PacketOrderL(player))
|
||||
.put(PacketOrderM.class, new PacketOrderM(player))
|
||||
.put(GroundSpoof.class, new GroundSpoof(player))
|
||||
.put(OffsetHandler.class, new OffsetHandler(player))
|
||||
.put(SuperDebug.class, new SuperDebug(player))
|
||||
@@ -176,6 +197,7 @@ public class CheckManager {
|
||||
.build();
|
||||
|
||||
blockPlaceCheck = new ImmutableClassToInstanceMap.Builder<BlockPlaceCheck>()
|
||||
.put(InventoryC.class, new InventoryC(player))
|
||||
.put(InvalidPlaceA.class, new InvalidPlaceA(player))
|
||||
.put(InvalidPlaceB.class, new InvalidPlaceB(player))
|
||||
.put(AirLiquidPlace.class, new AirLiquidPlace(player))
|
||||
@@ -185,7 +207,9 @@ public class CheckManager {
|
||||
.put(FabricatedPlace.class, new FabricatedPlace(player))
|
||||
.put(PositionPlace.class, new PositionPlace(player))
|
||||
.put(RotationPlace.class, new RotationPlace(player))
|
||||
.put(PacketOrderN.class, new PacketOrderN(player))
|
||||
.put(DuplicateRotPlace.class, new DuplicateRotPlace(player))
|
||||
.put(LineOfSightPlace.class, new LineOfSightPlace(player))
|
||||
.put(GhostBlockMitigation.class, new GhostBlockMitigation(player))
|
||||
.build();
|
||||
|
||||
@@ -219,6 +243,11 @@ public class CheckManager {
|
||||
.put(PositionBreakB.class, new PositionBreakB(player))
|
||||
.build();
|
||||
|
||||
// TODO put all modules with no listeners here
|
||||
ClassToInstanceMap<AbstractCheck> noneModules = new ImmutableClassToInstanceMap.Builder<AbstractCheck>()
|
||||
.put(HitboxDebugHandler.class, new HitboxDebugHandler(player))
|
||||
.build();
|
||||
|
||||
allChecks = new ImmutableClassToInstanceMap.Builder<AbstractCheck>()
|
||||
.putAll(packetChecks)
|
||||
.putAll(positionCheck)
|
||||
@@ -228,6 +257,7 @@ public class CheckManager {
|
||||
.putAll(blockPlaceCheck)
|
||||
.putAll(prePredictionChecks)
|
||||
.putAll(blockBreakChecks)
|
||||
.putAll(noneModules)
|
||||
.build();
|
||||
|
||||
init();
|
||||
@@ -338,6 +368,10 @@ public class CheckManager {
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends AbstractCheck> T getCheck(Class<T> check) {
|
||||
return (T) allChecks.get(check);
|
||||
}
|
||||
|
||||
public void onPostFlyingBlockBreak(final BlockBreak blockBreak) {
|
||||
for (BlockBreakCheck check : blockBreakChecks.values()) {
|
||||
check.onPostFlyingBlockBreak(blockBreak);
|
||||
@@ -353,6 +387,11 @@ public class CheckManager {
|
||||
return (T) packetChecks.get(check);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends BlockPlaceCheck> T getBlockPlaceCheck(Class<T> check) {
|
||||
return (T) blockPlaceCheck.get(check);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PacketCheck> T getPrePredictionCheck(Class<T> check) {
|
||||
return (T) prePredictionChecks.get(check);
|
||||
|
||||
@@ -5,10 +5,14 @@ import ac.grim.grimac.manager.init.Initable;
|
||||
import ac.grim.grimac.manager.init.load.PacketEventsInit;
|
||||
import ac.grim.grimac.manager.init.start.*;
|
||||
import ac.grim.grimac.manager.init.stop.TerminatePacketEvents;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
public class InitManager {
|
||||
|
||||
private final ImmutableList<Initable> initializersOnLoad;
|
||||
@@ -38,6 +42,7 @@ public class InitManager {
|
||||
.add(new PacketLimiter())
|
||||
.add(GrimAPI.INSTANCE.getDiscordManager())
|
||||
.add(GrimAPI.INSTANCE.getSpectateManager())
|
||||
.add(GrimAPI.INSTANCE.getViolationDatabaseManager())
|
||||
.add(new JavaVersion())
|
||||
.add(new ViaVersion())
|
||||
.add(new TAB())
|
||||
@@ -72,7 +77,12 @@ public class InitManager {
|
||||
try {
|
||||
initable.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
e.printStackTrace(pw);
|
||||
String stackTrace = sw.toString();
|
||||
|
||||
LogUtil.error("Failed to stop initializer: " + initable.getClass().getName() + stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,10 @@ public class PunishmentManager implements ConfigReloadable {
|
||||
|
||||
if (command.command.equals("[webhook]")) {
|
||||
GrimAPI.INSTANCE.getDiscordManager().sendAlert(player, verbose, check.getDisplayName(), vl);
|
||||
} else if (command.command.equals("[log]")) {
|
||||
int vls = (int) group.violations.values().stream().filter((e) -> e == check).count();
|
||||
String verboseWithoutGl = verbose.replaceAll(" /gl .*", "");
|
||||
GrimAPI.INSTANCE.getViolationDatabaseManager().logAlert(player, verboseWithoutGl, check.getDisplayName(), vls);
|
||||
} else if (command.command.equals("[proxy]")) {
|
||||
ProxyAlertMessenger.sendPluginMessage(replaceAlertPlaceholders(command.command, vl, check, proxyAlertString, verbose));
|
||||
} else {
|
||||
|
||||
@@ -111,8 +111,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
|
||||
// Setbacks aren't allowed
|
||||
if (player.disableGrim) return true;
|
||||
// Player has permission to cheat, permission not given to OP by default.
|
||||
if (player.bukkitPlayer != null && player.noSetbackPermission) return true;
|
||||
return false;
|
||||
return player.bukkitPlayer != null && player.noSetbackPermission;
|
||||
}
|
||||
|
||||
private void simulateFriction(Vector vector) {
|
||||
@@ -194,7 +193,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
|
||||
blockOffsets = true;
|
||||
}
|
||||
|
||||
SetBackData data = new SetBackData(new TeleportData(position, new RelativeFlag(0b11000), player.lastTransactionSent.get(), 0), player.xRot, player.yRot, clientVel, player.inVehicle(), false);
|
||||
SetBackData data = new SetBackData(new TeleportData(position, new Vector3d(), new RelativeFlag(0b11000), player.lastTransactionSent.get(), 0), player.xRot, player.yRot, clientVel, player.inVehicle(), false);
|
||||
sendSetback(data);
|
||||
}
|
||||
|
||||
@@ -248,7 +247,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
|
||||
data.getTeleportData().setTransaction(player.lastTransactionSent.get());
|
||||
|
||||
// Use provided transaction ID to make sure it can never desync, although there's no reason to do this
|
||||
addSentTeleport(new Location(null, position.getX(), y, position.getZ(), player.xRot % 360, player.yRot % 360), data.getTeleportData().getTransaction(), new RelativeFlag(0b11000), false, teleportId);
|
||||
addSentTeleport(new Location(null, position.getX(), y, position.getZ(), player.xRot % 360, player.yRot % 360), new Vector3d(), data.getTeleportData().getTransaction(), new RelativeFlag(0b11000), false, teleportId);
|
||||
// This must be done after setting the sent teleport, otherwise we lose velocity data
|
||||
requiredSetBack = data;
|
||||
// Send after tracking to fix race condition
|
||||
@@ -282,7 +281,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
|
||||
|
||||
// There seems to be a version difference in teleports past 30 million... just clamp the vector
|
||||
Vector3d clamped = VectorUtils.clampVector(new Vector3d(trueTeleportX, trueTeleportY, trueTeleportZ));
|
||||
double threshold = teleportPos.isRelative() ? player.getMovementThreshold() : 0;
|
||||
double threshold = teleportPos.isRelativePos() ? player.getMovementThreshold() : 0;
|
||||
boolean closeEnoughY = Math.abs(clamped.getY() - y) <= 1e-7 + threshold; // 1.7 rounding
|
||||
|
||||
if (player.lastTransactionReceived.get() == teleportPos.getTransaction() && Math.abs(clamped.getX() - x) <= threshold && closeEnoughY && Math.abs(clamped.getZ() - z) <= threshold) {
|
||||
@@ -384,8 +383,8 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
|
||||
!player.getSetbackTeleportUtil().hasAcceptedSpawnTeleport);
|
||||
}
|
||||
|
||||
public void addSentTeleport(Location position, int transaction, RelativeFlag flags, boolean plugin, int teleportId) {
|
||||
TeleportData data = new TeleportData(new Vector3d(position.getX(), position.getY(), position.getZ()), flags, transaction, teleportId);
|
||||
public void addSentTeleport(Location position, Vector3d velocity, int transaction, RelativeFlag flags, boolean plugin, int teleportId) {
|
||||
TeleportData data = new TeleportData(new Vector3d(position.getX(), position.getY(), position.getZ()), velocity, flags, transaction, teleportId);
|
||||
pendingTeleports.add(data);
|
||||
|
||||
Vector3d safePosition = new Vector3d(position.getX(), position.getY(), position.getZ());
|
||||
@@ -403,7 +402,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
|
||||
safePosition = safePosition.withZ(safePosition.getZ() + lastKnownGoodPosition.pos.getZ());
|
||||
}
|
||||
|
||||
data = new TeleportData(safePosition, new RelativeFlag(0b11000), transaction, teleportId);
|
||||
data = new TeleportData(safePosition, velocity, new RelativeFlag(0b11000), transaction, teleportId);
|
||||
requiredSetBack = new SetBackData(data, player.xRot, player.yRot, null, false, plugin);
|
||||
|
||||
this.lastKnownGoodPosition = new SetbackPosWithVector(safePosition, new Vector());
|
||||
|
||||
@@ -88,5 +88,14 @@ public class SpectateManager implements Initable {
|
||||
spectatingPlayers.remove(uuid);
|
||||
}
|
||||
|
||||
private record PreviousState(org.bukkit.GameMode gameMode, Location location) {}
|
||||
private static class PreviousState {
|
||||
public PreviousState(org.bukkit.GameMode gameMode, Location location) {
|
||||
this.gameMode = gameMode;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
private final org.bukkit.GameMode gameMode;
|
||||
private final Location location;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,6 +34,7 @@ public class ConfigManagerFileImpl implements ConfigManager, BasicReloadable {
|
||||
GrimAPI.INSTANCE.getPlugin().getDataFolder().mkdirs();
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
upgrade();
|
||||
config.addSource(GrimAC.class, "config", getConfigFile("config.yml"));
|
||||
config.addSource(GrimAC.class, "messages", getConfigFile("messages.yml"));
|
||||
config.addSource(GrimAC.class, "discord", getConfigFile("discord.yml"));
|
||||
@@ -83,7 +84,7 @@ public class ConfigManagerFileImpl implements ConfigManager, BasicReloadable {
|
||||
|
||||
configVersion = Integer.parseInt(configStringVersion);
|
||||
// TODO: Do we have to hardcode this?
|
||||
configString = configString.replaceAll("config-version: " + configStringVersion, "config-version: 9");
|
||||
configString = configString.replaceAll("config-version: " + configStringVersion, "config-version: 10");
|
||||
Files.write(config.toPath(), configString.getBytes());
|
||||
|
||||
upgradeModernConfig(config, configString, configVersion);
|
||||
@@ -125,6 +126,9 @@ public class ConfigManagerFileImpl implements ConfigManager, BasicReloadable {
|
||||
if (configVersion < 9) {
|
||||
newOffsetHandlingAntiKB(config, configString);
|
||||
}
|
||||
if (configVersion < 10) {
|
||||
addInventoryPunishments();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeLegacyTwoPointOne(File config) throws IOException {
|
||||
@@ -175,6 +179,28 @@ public class ConfigManagerFileImpl implements ConfigManager, BasicReloadable {
|
||||
}
|
||||
}
|
||||
|
||||
private void addInventoryPunishments() {
|
||||
File config = new File(GrimAPI.INSTANCE.getPlugin().getDataFolder(), "punishments.yml");
|
||||
String configString;
|
||||
if (config.exists()) {
|
||||
try {
|
||||
configString = new String(Files.readAllBytes(config.toPath()));
|
||||
|
||||
// If it works, it isn't stupid. Only replace it if it exactly matches the default config.
|
||||
String reachSection = " Reach:";
|
||||
String inventorySection = " Inventory:\n remove-violations-after: 300\n checks:\n - \"Inventory\"\n commands:\n - \"10:10 [alert]\"\n - \"20:20 [webhook]\"\n - \"20:20 [proxy]\"\n";
|
||||
|
||||
int index = configString.indexOf(reachSection);
|
||||
if (index != -1) {
|
||||
configString = configString.substring(0, index) + inventorySection + configString.substring(index);
|
||||
}
|
||||
|
||||
Files.write(config.toPath(), configString.getBytes());
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fixBadPacketsAndAdjustPingConfig(File config, String configString) {
|
||||
try {
|
||||
configString = configString.replaceAll("max-ping: \\d+", "max-transaction-time: 60");
|
||||
|
||||
@@ -11,15 +11,31 @@ import com.github.retrooper.packetevents.protocol.item.enchantment.type.Enchantm
|
||||
import com.github.retrooper.packetevents.protocol.item.type.ItemTypes;
|
||||
import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
|
||||
import com.github.retrooper.packetevents.util.PEVersion;
|
||||
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class PacketEventsInit implements Initable {
|
||||
|
||||
PEVersion NEWEST_UNSUPPORTED_PE_VERSION = new PEVersion(2, 7, 0);
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LogUtil.info("Loading PacketEvents...");
|
||||
|
||||
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(GrimAPI.INSTANCE.getPlugin()));
|
||||
|
||||
if (!checkPacketEventsVersion()) {
|
||||
GrimAPI.INSTANCE.signalCriticalFailure();
|
||||
throw new RuntimeException("\n" +
|
||||
"******************************************************\n" +
|
||||
"GrimAC requires PacketEvents > " + NEWEST_UNSUPPORTED_PE_VERSION + "\n" +
|
||||
"Please update PacketEvents to a compatible version.\n" +
|
||||
"This is a critical error. GrimAC will be disabled.\n" +
|
||||
"*****************************************************");
|
||||
}
|
||||
|
||||
PacketEvents.getAPI().getSettings()
|
||||
.fullStackTrace(true)
|
||||
.kickOnPacketException(true)
|
||||
@@ -27,6 +43,7 @@ public class PacketEventsInit implements Initable {
|
||||
.reEncodeByDefault(false)
|
||||
.debug(false);
|
||||
PacketEvents.getAPI().load();
|
||||
|
||||
// This may seem useless, but it causes java to start loading stuff async before we need it
|
||||
Executors.defaultThreadFactory().newThread(() -> {
|
||||
StateTypes.AIR.getName();
|
||||
@@ -38,4 +55,9 @@ public class PacketEventsInit implements Initable {
|
||||
ParticleTypes.DUST.getName();
|
||||
}).start();
|
||||
}
|
||||
|
||||
private boolean checkPacketEventsVersion() {
|
||||
PEVersion peVersion = PacketEvents.getAPI().getVersion();
|
||||
return peVersion.isNewerThan(NEWEST_UNSUPPORTED_PE_VERSION);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.commands.*;
|
||||
import ac.grim.grimac.manager.init.Initable;
|
||||
import co.aikar.commands.PaperCommandManager;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public class CommandRegister implements Initable {
|
||||
@Override
|
||||
@@ -27,11 +26,8 @@ public class CommandRegister implements Initable {
|
||||
commandManager.registerCommand(new GrimVersion());
|
||||
commandManager.registerCommand(new GrimDump());
|
||||
commandManager.registerCommand(new GrimBrands());
|
||||
commandManager.registerCommand(new GrimHistory());
|
||||
|
||||
commandManager.getCommandCompletions().registerCompletion("stopspectating", GrimStopSpectating.completionHandler);
|
||||
|
||||
if (GrimAPI.INSTANCE.getConfigManager().getConfig().getBooleanElse("check-for-updates", true)) {
|
||||
GrimVersion.checkForUpdatesAsync(Bukkit.getConsoleSender());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public class PacketManager implements Initable {
|
||||
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerJoinQuit());
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketPingListener());
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerWindow());
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerDigging());
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerAttack());
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketEntityAction());
|
||||
|
||||
@@ -8,9 +8,7 @@ import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class PlaceholderAPIExpansion extends PlaceholderExpansion {
|
||||
@@ -37,7 +35,7 @@ public class PlaceholderAPIExpansion extends PlaceholderExpansion {
|
||||
public @NotNull List<String> getPlaceholders() {
|
||||
Set<String> placeholders = GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements().keySet();
|
||||
placeholders.addAll(GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements().keySet());
|
||||
return List.copyOf(placeholders);
|
||||
return Collections.unmodifiableList(new ArrayList<>(placeholders));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,7 +49,8 @@ public class PlaceholderAPIExpansion extends PlaceholderExpansion {
|
||||
}
|
||||
}
|
||||
|
||||
if (offlinePlayer instanceof Player player) {
|
||||
if (offlinePlayer instanceof Player) {
|
||||
Player player = (Player) offlinePlayer;
|
||||
GrimPlayer grimPlayer = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(player);
|
||||
if (grimPlayer == null) return null;
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ public class TickEndEvent implements Initable, Listener {
|
||||
// but whatever. At least plugins can't break it, I guess.
|
||||
//
|
||||
// Pledge injects into another list, so we should be safe injecting into this one
|
||||
List<?> wrapper = Collections.synchronizedList(new HookedListWrapper<>(endOfTickObject) {
|
||||
List<?> wrapper = Collections.synchronizedList(new HookedListWrapper<Object>(endOfTickObject) {
|
||||
@Override
|
||||
public void onIterator() {
|
||||
tickAllPlayers();
|
||||
|
||||
@@ -13,9 +13,15 @@ public class ExemptElytraFeature extends GrimFeature {
|
||||
@Override
|
||||
public void setState(GrimPlayer player, ConfigManager config, FeatureState state) {
|
||||
switch (state) {
|
||||
case ENABLED -> player.setExemptElytra(true);
|
||||
case DISABLED -> player.setExemptElytra(false);
|
||||
default -> player.setExemptElytra(isEnabledInConfig(player, config));
|
||||
case ENABLED:
|
||||
player.setExemptElytra(true);
|
||||
break;
|
||||
case DISABLED:
|
||||
player.setExemptElytra(false);
|
||||
break;
|
||||
default:
|
||||
player.setExemptElytra(isEnabledInConfig(player, config));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-3
@@ -13,9 +13,15 @@ public class ExperimentalChecksFeature extends GrimFeature {
|
||||
@Override
|
||||
public void setState(GrimPlayer player, ConfigManager config, FeatureState state) {
|
||||
switch (state) {
|
||||
case ENABLED -> player.setExperimentalChecks(true);
|
||||
case DISABLED -> player.setExperimentalChecks(false);
|
||||
default -> player.setExperimentalChecks(isEnabledInConfig(player, config));
|
||||
case ENABLED:
|
||||
player.setExperimentalChecks(true);
|
||||
break;
|
||||
case DISABLED:
|
||||
player.setExperimentalChecks(false);
|
||||
break;
|
||||
default:
|
||||
player.setExperimentalChecks(isEnabledInConfig(player, config));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import ac.grim.grimac.player.GrimPlayer;
|
||||
|
||||
public class ClearRecentlyUpdatedBlocks implements Tickable {
|
||||
|
||||
private static final int maxTickAge = 2;
|
||||
private static final int maxTickAge = 3;
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package ac.grim.grimac.manager.violationdatabase;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class MySQLViolationDatabase implements ViolationDatabase {
|
||||
|
||||
private final Plugin plugin;
|
||||
private HikariDataSource dataSource;
|
||||
|
||||
public MySQLViolationDatabase(Plugin plugin, String url, String database, String username, String password) {
|
||||
this.plugin = plugin;
|
||||
setupDataSource(url, database, username, password);
|
||||
}
|
||||
|
||||
private void setupDataSource(String url, String database, String username, String password) {
|
||||
HikariConfig config = new HikariConfig();
|
||||
config.setJdbcUrl("jdbc:mysql://" + url + "/" + database);
|
||||
config.setUsername(username);
|
||||
config.setPassword(password);
|
||||
config.addDataSourceProperty("cachePrepStmts", "true");
|
||||
config.addDataSourceProperty("prepStmtCacheSize", "250");
|
||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
|
||||
config.setMaximumPoolSize(10);
|
||||
config.setAutoCommit(true);
|
||||
dataSource = new HikariDataSource(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
connection.prepareStatement(
|
||||
"CREATE TABLE IF NOT EXISTS violations(" +
|
||||
"id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, " +
|
||||
"server VARCHAR(255) NOT NULL, " +
|
||||
"uuid CHAR(36) NOT NULL, " +
|
||||
"check_name TEXT NOT NULL, " +
|
||||
"verbose TEXT NOT NULL, " +
|
||||
"vl INTEGER NOT NULL, " +
|
||||
"created_at BIGINT NOT NULL" +
|
||||
")"
|
||||
).execute();
|
||||
|
||||
connection.prepareStatement(
|
||||
"CREATE INDEX IF NOT EXISTS idx_violations_uuid ON violations(uuid);"
|
||||
).execute();
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to generate violations database:", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void logAlert(GrimPlayer player, String verbose, String checkName, int vls) {
|
||||
try (Connection connection = dataSource.getConnection();
|
||||
PreparedStatement insertAlert = connection.prepareStatement(
|
||||
"INSERT INTO violations (server, uuid, check_name, verbose, vl, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
) {
|
||||
insertAlert.setString(1, GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("history.server-name", "Prison"));
|
||||
insertAlert.setString(2, player.getUniqueId().toString());
|
||||
insertAlert.setString(3, checkName);
|
||||
insertAlert.setString(4, verbose);
|
||||
insertAlert.setInt(5, vls);
|
||||
insertAlert.setLong(6, System.currentTimeMillis());
|
||||
insertAlert.execute();
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to log alert", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getLogCount(UUID player) {
|
||||
try (Connection connection = dataSource.getConnection();
|
||||
PreparedStatement countLogs = connection.prepareStatement(
|
||||
"SELECT COUNT(*) FROM violations WHERE uuid = ?"
|
||||
)
|
||||
) {
|
||||
countLogs.setString(1, player.toString());
|
||||
ResultSet result = countLogs.executeQuery();
|
||||
if (result.next()) {
|
||||
return result.getInt(1);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to count logs", ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Violation> getViolations(UUID player, int page, int limit) {
|
||||
try (Connection connection = dataSource.getConnection();
|
||||
PreparedStatement fetchLogs = connection.prepareStatement(
|
||||
"SELECT server, uuid, check_name, verbose, vl, created_at FROM violations" +
|
||||
" WHERE uuid = ? ORDER BY created_at DESC LIMIT ? OFFSET ?"
|
||||
)
|
||||
) {
|
||||
fetchLogs.setString(1, player.toString());
|
||||
fetchLogs.setInt(2, limit);
|
||||
fetchLogs.setInt(3, (page - 1) * limit);
|
||||
return Violation.fromResultSet(fetchLogs.executeQuery());
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to fetch logs", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
if (dataSource != null && !dataSource.isClosed()) {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package ac.grim.grimac.manager.violationdatabase;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class SQLiteViolationDatabase implements ViolationDatabase {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
private Connection openConnection;
|
||||
|
||||
public SQLiteViolationDatabase(@NotNull Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
try {
|
||||
// Init sqlite
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
} catch (ClassNotFoundException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Could not load SQLite driver", e);
|
||||
}
|
||||
try (Connection connection = getConnection()) {
|
||||
connection.prepareStatement(
|
||||
"CREATE TABLE IF NOT EXISTS violations(" +
|
||||
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " +
|
||||
"server VARCHAR(255) NOT NULL, " +
|
||||
"uuid CHARACTER(36) NOT NULL, " +
|
||||
"check_name TEXT NOT NULL, " +
|
||||
"verbose TEXT NOT NULL, " +
|
||||
"vl INTEGER NOT NULL, " +
|
||||
"created_at BIGINT NOT NULL" +
|
||||
")"
|
||||
).execute();
|
||||
|
||||
connection.prepareStatement(
|
||||
"CREATE INDEX IF NOT EXISTS idx_violations_uuid ON violations(uuid)"
|
||||
).execute();
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to generate violations database:", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void logAlert(GrimPlayer player, String verbose, String checkName, int vls) {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement insertLog = connection.prepareStatement(
|
||||
"INSERT INTO violations (server, uuid, check_name, verbose, vl, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
) {
|
||||
insertLog.setString(1, GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("history.server-name", "Prison"));
|
||||
insertLog.setString(2, player.getUniqueId().toString());
|
||||
insertLog.setString(3, verbose);
|
||||
insertLog.setString(4, checkName);
|
||||
insertLog.setInt(5, vls);
|
||||
insertLog.setLong(6, System.currentTimeMillis());
|
||||
|
||||
insertLog.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to insert violation:", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized int getLogCount(UUID player) {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement fetchLogs = connection.prepareStatement(
|
||||
"SELECT COUNT(*) FROM violations WHERE uuid = ?"
|
||||
)
|
||||
) {
|
||||
fetchLogs.setString(1, player.toString());
|
||||
ResultSet resultSet = fetchLogs.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
return resultSet.getInt(1);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to fetch number of violations:", ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Violation> getViolations(UUID player, int page, int limit) {
|
||||
List<Violation> violations = new ArrayList<>();
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement fetchLogs = connection.prepareStatement(
|
||||
"SELECT server, uuid, check_name, verbose, vl, created_at FROM violations" +
|
||||
" WHERE uuid = ? ORDER BY created_at DESC LIMIT ? OFFSET ?"
|
||||
)
|
||||
) {
|
||||
fetchLogs.setString(1, player.toString());
|
||||
fetchLogs.setInt(2, limit);
|
||||
fetchLogs.setInt(3, (page - 1) * limit);
|
||||
|
||||
return Violation.fromResultSet(fetchLogs.executeQuery());
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to fetch violations:", ex);
|
||||
}
|
||||
|
||||
return violations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
try {
|
||||
if (openConnection != null && !openConnection.isClosed()) {
|
||||
openConnection.close();
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to close connection", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized Connection getConnection() throws SQLException {
|
||||
if (openConnection == null || openConnection.isClosed()) {
|
||||
openConnection = openConnection();
|
||||
}
|
||||
return openConnection;
|
||||
}
|
||||
|
||||
protected Connection openConnection() throws SQLException {
|
||||
return DriverManager.getConnection("jdbc:sqlite:" + plugin.getDataFolder().getAbsolutePath() + File.separator + "violations.sqlite");}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ac.grim.grimac.manager.violationdatabase;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
public class Violation {
|
||||
|
||||
private final String serverName;
|
||||
private final UUID playerUUID;
|
||||
private final String checkName;
|
||||
private final String verbose;
|
||||
private final int vl;
|
||||
private final Date createdAt;
|
||||
|
||||
public static List<Violation> fromResultSet(ResultSet resultSet) throws SQLException {
|
||||
List<Violation> violations = new ArrayList<>();
|
||||
while(resultSet.next()) {
|
||||
String server = resultSet.getString("server");
|
||||
UUID player = UUID.fromString(resultSet.getString("uuid"));
|
||||
String checkName = resultSet.getString("check_name");
|
||||
String verbose = resultSet.getString("verbose");
|
||||
int vl = resultSet.getInt("vl");
|
||||
Date createdAt = new Date(resultSet.getLong("created_at"));
|
||||
|
||||
violations.add(new Violation(server, player, checkName, verbose, vl, createdAt));
|
||||
}
|
||||
|
||||
return violations;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ac.grim.grimac.manager.violationdatabase;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface ViolationDatabase {
|
||||
|
||||
void connect();
|
||||
|
||||
void logAlert(GrimPlayer player, String verbose, String checkName, int vls);
|
||||
|
||||
int getLogCount(UUID player);
|
||||
|
||||
List<Violation> getViolations(UUID player, int page, int limit);
|
||||
|
||||
void disconnect();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package ac.grim.grimac.manager.violationdatabase;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.api.config.ConfigManager;
|
||||
import ac.grim.grimac.manager.init.Initable;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import io.github.retrooper.packetevents.util.folia.FoliaScheduler;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ViolationDatabaseManager implements Initable {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
private ViolationDatabase database;
|
||||
|
||||
public ViolationDatabaseManager(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
ConfigManager config = GrimAPI.INSTANCE.getConfigManager().getConfig();
|
||||
String dbType = config.getStringElse("history.database.type", "SQLITE").toUpperCase();
|
||||
|
||||
if (dbType.equals("SQLITE")) {
|
||||
this.database = new SQLiteViolationDatabase(plugin);
|
||||
} else if (dbType.equals("MYSQL")) {
|
||||
String url = config.getStringElse("history.database.host", "jdbc:mysql://localhost:3306/");
|
||||
String database = config.getStringElse("history.database.database", "grimac");
|
||||
String username = config.getStringElse("history.database.username", "root");
|
||||
String password = config.getStringElse("history.database.password", "password");
|
||||
|
||||
this.database = new MySQLViolationDatabase(plugin, url, database, username, password);
|
||||
} else {
|
||||
this.database = new SQLiteViolationDatabase(plugin);
|
||||
plugin.getLogger().log(Level.SEVERE, "Invalid database type: " + dbType + ". Defaulting to SQLite");
|
||||
}
|
||||
|
||||
this.database.connect();
|
||||
}
|
||||
|
||||
public void logAlert(GrimPlayer player, String verbose, String checkName, int vls) {
|
||||
FoliaScheduler.getAsyncScheduler().runNow(plugin, __ -> database.logAlert(player, verbose, checkName, vls));
|
||||
}
|
||||
|
||||
public int getLogCount(UUID player) {
|
||||
return database.getLogCount(player);
|
||||
}
|
||||
|
||||
public List<Violation> getViolations(UUID player, int page, int limit) {
|
||||
return database.getViolations(player, page, limit);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,7 +9,8 @@ import ac.grim.grimac.api.handler.ResyncHandler;
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.impl.aim.processor.AimProcessor;
|
||||
import ac.grim.grimac.checks.impl.misc.ClientBrand;
|
||||
import ac.grim.grimac.checks.impl.misc.TransactionOrder;
|
||||
import ac.grim.grimac.checks.impl.packetorder.PacketOrderD;
|
||||
import ac.grim.grimac.checks.impl.packetorder.PacketOrderProcessor;
|
||||
import ac.grim.grimac.events.packets.CheckManagerListener;
|
||||
import ac.grim.grimac.manager.*;
|
||||
import ac.grim.grimac.manager.player.features.FeatureManagerImpl;
|
||||
@@ -28,11 +29,13 @@ import ac.grim.grimac.utils.data.packetentity.PacketEntitySelf;
|
||||
import ac.grim.grimac.utils.data.tags.SyncedTags;
|
||||
import ac.grim.grimac.utils.enums.FluidTag;
|
||||
import ac.grim.grimac.utils.enums.Pose;
|
||||
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
|
||||
import ac.grim.grimac.utils.latency.*;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.math.TrigHandler;
|
||||
import ac.grim.grimac.utils.nmsutil.BlockProperties;
|
||||
import ac.grim.grimac.utils.nmsutil.GetBoundingBox;
|
||||
import ac.grim.grimac.utils.nmsutil.ReachUtils;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
@@ -106,6 +109,7 @@ public class GrimPlayer implements GrimUser {
|
||||
// End manager like classes
|
||||
public Vector clientVelocity = new Vector();
|
||||
PacketTracker packetTracker;
|
||||
public final PacketOrderProcessor packetOrderProcessor = new PacketOrderProcessor(this);
|
||||
private long transactionPing = 0;
|
||||
public long lastTransSent = 0;
|
||||
public long lastTransReceived = 0;
|
||||
@@ -221,6 +225,9 @@ public class GrimPlayer implements GrimUser {
|
||||
// possibleEyeHeights[0] = Standing eye heights, [1] = Sneaking. [2] = Elytra, Swimming, and Riptide Trident which only exists in 1.9+
|
||||
public double[][] possibleEyeHeights = new double[3][];
|
||||
public int totalFlyingPacketsSent;
|
||||
public boolean hasInventoryOpen;
|
||||
public long lastInventoryOpen;
|
||||
public InventoryDesyncStatus inventoryDesyncStatus;
|
||||
public Queue<BlockPlaceSnapshot> placeUseItemPackets = new LinkedBlockingQueue<>();
|
||||
public Queue<BlockBreak> queuedBreaks = new LinkedBlockingQueue<>();
|
||||
public PlayerBlockHistory blockHistory = new PlayerBlockHistory();
|
||||
@@ -360,7 +367,7 @@ public class GrimPlayer implements GrimUser {
|
||||
if (packetTracker != null) packetTracker.setIntervalPackets(packetTracker.getIntervalPackets() - 1);
|
||||
|
||||
if (skipped > 0 && System.currentTimeMillis() - joinTime > 5000)
|
||||
checkManager.getPacketCheck(TransactionOrder.class).flagAndAlert("skipped: " + skipped);
|
||||
checkManager.getPacketCheck(PacketOrderD.class).flagAndAlert("skipped: " + skipped);
|
||||
|
||||
do {
|
||||
data = transactionsSent.poll();
|
||||
@@ -466,7 +473,8 @@ public class GrimPlayer implements GrimUser {
|
||||
|
||||
public void disconnect(Component reason) {
|
||||
String textReason;
|
||||
if (reason instanceof TranslatableComponent translatableComponent) {
|
||||
if (reason instanceof TranslatableComponent) {
|
||||
TranslatableComponent translatableComponent = (TranslatableComponent) reason;
|
||||
textReason = translatableComponent.key();
|
||||
} else {
|
||||
textReason = LegacyComponentSerializer.legacySection().serialize(reason);
|
||||
@@ -571,7 +579,7 @@ public class GrimPlayer implements GrimUser {
|
||||
public ClientVersion getClientVersion() {
|
||||
ClientVersion ver = user.getClientVersion();
|
||||
// If temporarily null, assume server version...
|
||||
return Objects.requireNonNullElseGet(ver, () -> ClientVersion.getById(PacketEvents.getAPI().getServerManager().getVersion().getProtocolVersion()));
|
||||
return ver != null ? ver : ClientVersion.getById(PacketEvents.getAPI().getServerManager().getVersion().getProtocolVersion());
|
||||
}
|
||||
|
||||
// Alright, someone at mojang decided to not send a flying packet every tick with 1.9
|
||||
@@ -607,15 +615,60 @@ public class GrimPlayer implements GrimUser {
|
||||
return this.isSneaking ? this.possibleEyeHeights[1] : this.possibleEyeHeights[0];
|
||||
} else {
|
||||
// 1.8 players just have their pose set to standing all the time
|
||||
return switch (pose) {
|
||||
case FALL_FLYING, // Elytra gliding
|
||||
SPIN_ATTACK, // Riptide trident
|
||||
SWIMMING -> // Swimming (includes crawling in 1.14+)
|
||||
this.possibleEyeHeights[2]; // [swimming/gliding/riptide height, standing height, sneaking height]
|
||||
case NINE_CROUCHING, CROUCHING -> this.possibleEyeHeights[1]; // [sneaking height, standing height, swimming/gliding/riptide height]
|
||||
default -> this.possibleEyeHeights[0]; // [standing height, sneaking height, swimming/gliding/riptide height]
|
||||
switch (pose) {
|
||||
case FALL_FLYING: // Elytra gliding
|
||||
case SPIN_ATTACK: // Riptide trident
|
||||
case SWIMMING: // Swimming (includes crawling in 1.14+)
|
||||
return this.possibleEyeHeights[2]; // [swimming/gliding/riptide height, standing height, sneaking height]
|
||||
case NINE_CROUCHING:
|
||||
case CROUCHING:
|
||||
return this.possibleEyeHeights[1]; // [sneaking height, standing height, swimming/gliding/riptide height]
|
||||
default:
|
||||
return this.possibleEyeHeights[0]; // [standing height, sneaking height, swimming/gliding/riptide height]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1.8-1.10.2 specific mouse delay fix (MC-67665)
|
||||
// https://bugs.mojang.com/browse/MC-67665
|
||||
// 1.9-1.21.1 specific desync due to skipped ticks
|
||||
// Players can be a tick behind on both pitch and yaw together
|
||||
// 1.21.2+ added end tick input packet, fixing skipped tick issues
|
||||
public Vector[] getPossibleLookVectors(boolean isPrediction) {
|
||||
// If we are a tick behind, we don't know their next look so only use current
|
||||
if (isPrediction) {
|
||||
return new Vector[]{ReachUtils.getLook(this, this.xRot, this.yRot)};
|
||||
}
|
||||
|
||||
// 1.9-1.10.2: All three vectors (normal, mouse delay, tick desync)
|
||||
if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) &&
|
||||
this.getClientVersion().isOlderThan(ClientVersion.V_1_11)) {
|
||||
return new Vector[]{
|
||||
ReachUtils.getLook(this, this.xRot, this.yRot),
|
||||
ReachUtils.getLook(this, this.lastXRot, this.yRot),
|
||||
ReachUtils.getLook(this, this.lastXRot, this.lastYRot)
|
||||
};
|
||||
}
|
||||
// 1.11-1.21.1: Two vectors (normal, tick desync)
|
||||
else if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_11) &&
|
||||
this.getClientVersion().isOlderThan(ClientVersion.V_1_21_2)) {
|
||||
return new Vector[]{
|
||||
ReachUtils.getLook(this, this.xRot, this.yRot),
|
||||
ReachUtils.getLook(this, this.lastXRot, this.lastYRot)
|
||||
};
|
||||
}
|
||||
// 1.8: Two vectors (normal, mouse delay)
|
||||
else if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8) &&
|
||||
this.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
|
||||
return new Vector[]{
|
||||
ReachUtils.getLook(this, this.xRot, this.yRot),
|
||||
ReachUtils.getLook(this, this.lastXRot, this.yRot)
|
||||
};
|
||||
}
|
||||
// 1.7 and 1.21.2+: Just normal vector
|
||||
else {
|
||||
return new Vector[]{ReachUtils.getLook(this, this.xRot, this.yRot)};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -87,16 +87,14 @@ public class MovementCheckRunner extends Check implements PositionCheck {
|
||||
// Reset velocities
|
||||
// Teleporting a vehicle does not reset its velocity
|
||||
if (!player.inVehicle()) {
|
||||
if (update.getTeleportData() == null || !update.getTeleportData().isRelativeX()) {
|
||||
if (update.getTeleportData() == null) {
|
||||
player.clientVelocity.setX(0);
|
||||
}
|
||||
if (update.getTeleportData() == null || !update.getTeleportData().isRelativeY()) {
|
||||
player.clientVelocity.setY(0);
|
||||
player.clientVelocity.setZ(0);
|
||||
player.lastWasClimbing = 0; // Vertical movement reset
|
||||
player.canSwimHop = false; // Vertical movement reset
|
||||
}
|
||||
if (update.getTeleportData() == null || !update.getTeleportData().isRelativeZ()) {
|
||||
player.clientVelocity.setZ(0);
|
||||
} else {
|
||||
update.getTeleportData().modifyVector(player, player.clientVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +258,8 @@ public class MovementCheckRunner extends Check implements PositionCheck {
|
||||
// When in control of the entity, the player sets the entity position to their current position
|
||||
riding.setPositionRaw(player, new SimpleCollisionBox(player.x, player.y, player.z, player.x, player.y, player.z));
|
||||
|
||||
if (riding instanceof PacketEntityTrackXRot boat) {
|
||||
if (riding instanceof PacketEntityTrackXRot) {
|
||||
PacketEntityTrackXRot boat = (PacketEntityTrackXRot) riding;
|
||||
boat.packetYaw = player.xRot;
|
||||
boat.interpYaw = player.xRot;
|
||||
boat.steps = 0;
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class PlayerBaseTick {
|
||||
@@ -142,11 +143,12 @@ public final class PlayerBaseTick {
|
||||
// Pre-1.17 clients don't have powder snow and therefore don't desync
|
||||
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_16_4)) return;
|
||||
|
||||
final ValuedAttribute playerSpeed = player.compensatedEntities.self.getAttribute(Attributes.MOVEMENT_SPEED).orElseThrow();
|
||||
final ValuedAttribute playerSpeed = player.compensatedEntities.self.getAttribute(Attributes.MOVEMENT_SPEED).orElseThrow(() -> new NoSuchElementException("No value present"));
|
||||
|
||||
|
||||
// Might be null after respawn?
|
||||
final Optional<WrapperPlayServerUpdateAttributes.Property> property = playerSpeed.property();
|
||||
if (property.isEmpty()) return;
|
||||
if (!property.isPresent()) return;
|
||||
|
||||
// The client first desync's this attribute
|
||||
property.get().getModifiers().removeIf(modifier -> modifier.getUUID().equals(CompensatedEntities.SNOW_MODIFIER_UUID) || modifier.getName().getKey().equals("powder_snow"));
|
||||
@@ -356,12 +358,22 @@ public final class PlayerBaseTick {
|
||||
double d7 = direction2 == BlockFace.WEST || direction2 == BlockFace.EAST ? relativeXMovement : relativeZMovement;
|
||||
d6 = direction2 == BlockFace.EAST || direction2 == BlockFace.SOUTH ? 1.0 - d7 : d7;
|
||||
// d7 and d6 flip the movement direction based on desired movement direction
|
||||
boolean doesSuffocate = switch (direction2) {
|
||||
case EAST -> suffocatesAt(player, blockX + 1, blockZ);
|
||||
case WEST -> suffocatesAt(player, blockX - 1, blockZ);
|
||||
case NORTH -> suffocatesAt(player, blockX, blockZ - 1);
|
||||
default -> suffocatesAt(player, blockX, blockZ + 1);
|
||||
};
|
||||
boolean doesSuffocate;
|
||||
switch (direction2) {
|
||||
case EAST:
|
||||
doesSuffocate = suffocatesAt(player, blockX + 1, blockZ);
|
||||
break;
|
||||
case WEST:
|
||||
doesSuffocate = suffocatesAt(player, blockX - 1, blockZ);
|
||||
break;
|
||||
case NORTH:
|
||||
doesSuffocate = suffocatesAt(player, blockX, blockZ - 1);
|
||||
break;
|
||||
case SOUTH:
|
||||
default:
|
||||
doesSuffocate = suffocatesAt(player, blockX, blockZ + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (d6 >= lowestValue || doesSuffocate) continue;
|
||||
lowestValue = d6;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ac.grim.grimac.predictionengine;
|
||||
|
||||
import ac.grim.grimac.checks.impl.combat.Reach;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.predictionengine.predictions.PredictionEngine;
|
||||
import ac.grim.grimac.utils.collisions.CollisionData;
|
||||
@@ -16,6 +17,7 @@ import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState
|
||||
import com.github.retrooper.packetevents.protocol.world.states.defaulttags.BlockTags;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bukkit.util.Vector;
|
||||
@@ -104,6 +106,7 @@ public class PointThreeEstimator {
|
||||
private boolean isNearHorizontalFlowingLiquid = false; // We can't calculate the direction, only a toggle
|
||||
private boolean isNearVerticalFlowingLiquid = false; // We can't calculate exact values, once again a toggle
|
||||
private boolean isNearBubbleColumn = false; // We can't calculate exact values once again
|
||||
public boolean isNearNetherPortal = false; // We can't calculate exact values once again
|
||||
|
||||
private int maxPositiveLevitation = Integer.MIN_VALUE; // Positive potion effects [0, 128]
|
||||
private int minNegativeLevitation = Integer.MAX_VALUE; // Negative potion effects [-127, -1]r
|
||||
@@ -152,6 +155,9 @@ public class PointThreeEstimator {
|
||||
isNearFluid = true;
|
||||
}
|
||||
|
||||
player.checkManager.getPacketCheck(Reach.class).handleBlockChange(new Vector3i(x, y, z), state);
|
||||
|
||||
|
||||
if (pointThreeBox.isIntersected(new SimpleCollisionBox(x, y, z))) {
|
||||
// https://github.com/MWHunter/Grim/issues/613
|
||||
int controllingEntityId = player.inVehicle() ? player.getRidingVehicleId() : player.entityID;
|
||||
@@ -247,6 +253,7 @@ public class PointThreeEstimator {
|
||||
isNearClimbable = false;
|
||||
isNearBubbleColumn = false;
|
||||
isNearFluid = false;
|
||||
isNearNetherPortal = false;
|
||||
|
||||
// Check for flowing water
|
||||
Collisions.hasMaterial(player, pointThreeBox, (pair) -> {
|
||||
@@ -268,6 +275,10 @@ public class PointThreeEstimator {
|
||||
isNearFluid = true;
|
||||
}
|
||||
|
||||
if (state.getType() == StateTypes.NETHER_PORTAL) {
|
||||
isNearNetherPortal = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -305,7 +305,8 @@ public class UncertaintyHandler {
|
||||
// This is the end of that section.
|
||||
|
||||
// I can't figure out how the client exactly tracks boost time
|
||||
if (player.compensatedEntities.self.getRiding() instanceof PacketEntityRideable vehicle) {
|
||||
if (player.compensatedEntities.self.getRiding() instanceof PacketEntityRideable) {
|
||||
PacketEntityRideable vehicle = (PacketEntityRideable) player.compensatedEntities.self.getRiding();
|
||||
if (vehicle.currentBoostTime < vehicle.boostTimeMax + 20)
|
||||
offset -= 0.01;
|
||||
}
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUp
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class MovementTickerStrider extends MovementTickerRideable {
|
||||
|
||||
@@ -61,7 +62,7 @@ public class MovementTickerStrider extends MovementTickerRideable {
|
||||
|
||||
// Client desyncs the attribute
|
||||
// Again I don't know when this was changed, or whether it always existed, so I will just put it behind 1.20+
|
||||
final ValuedAttribute movementSpeedAttr = strider.getAttribute(Attributes.MOVEMENT_SPEED).orElseThrow();
|
||||
final ValuedAttribute movementSpeedAttr = strider.getAttribute(Attributes.MOVEMENT_SPEED).orElseThrow(() -> new NoSuchElementException("Attribute not found"));
|
||||
float updatedMovementSpeed = (float) movementSpeedAttr.get();
|
||||
if (newSpeed) {
|
||||
final WrapperPlayServerUpdateAttributes.Property lastProperty = movementSpeedAttr.property().orElse(null);
|
||||
|
||||
@@ -424,52 +424,63 @@ public class PredictionEngine {
|
||||
// Order priority (to avoid false positives and false flagging future predictions):
|
||||
// Knockback and explosions
|
||||
// 0.03 ticks
|
||||
// Movement without input
|
||||
// Normal movement
|
||||
// First bread knockback and explosions
|
||||
// Flagging groundspoof
|
||||
// Flagging flip items
|
||||
if (a.isExplosion())
|
||||
aScore -= 5;
|
||||
aScore -= 10;
|
||||
|
||||
if (a.isKnockback())
|
||||
aScore -= 5;
|
||||
aScore -= 10;
|
||||
|
||||
if (b.isExplosion())
|
||||
bScore -= 5;
|
||||
bScore -= 10;
|
||||
|
||||
if (b.isKnockback())
|
||||
bScore -= 5;
|
||||
bScore -= 10;
|
||||
|
||||
if (a.isFirstBreadExplosion())
|
||||
aScore += 1;
|
||||
aScore += 2;
|
||||
|
||||
if (b.isFirstBreadExplosion())
|
||||
bScore += 1;
|
||||
bScore += 2;
|
||||
|
||||
if (a.isFirstBreadKb())
|
||||
aScore += 1;
|
||||
aScore += 2;
|
||||
|
||||
if (b.isFirstBreadKb())
|
||||
bScore += 1;
|
||||
bScore += 2;
|
||||
|
||||
if (a.isFlipItem())
|
||||
aScore += 3;
|
||||
aScore += 6;
|
||||
|
||||
if (b.isFlipItem())
|
||||
bScore += 3;
|
||||
bScore += 6;
|
||||
|
||||
if (a.isZeroPointZeroThree())
|
||||
aScore -= 1;
|
||||
aScore -= 2;
|
||||
|
||||
if (b.isZeroPointZeroThree())
|
||||
bScore -= 2;
|
||||
|
||||
if (a.isWithInput() || a.isJump())
|
||||
aScore += 1;
|
||||
else
|
||||
aScore -= 1;
|
||||
|
||||
if (b.isWithInput() || b.isJump())
|
||||
bScore += 1;
|
||||
else
|
||||
bScore -= 1;
|
||||
|
||||
// If the player is on the ground but the vector leads the player off the ground
|
||||
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && a.vector.getY() >= 0)
|
||||
aScore += 2;
|
||||
aScore += 4;
|
||||
|
||||
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vector.getY() >= 0)
|
||||
bScore += 2;
|
||||
bScore += 4;
|
||||
|
||||
if (aScore != bScore)
|
||||
return Integer.compare(aScore, bScore);
|
||||
@@ -756,9 +767,9 @@ public class PredictionEngine {
|
||||
if (loopSlowed == 1 && !possibleLastTickOutput.isZeroPointZeroThree()) continue;
|
||||
for (int strafe = strafeMin; strafe <= strafeMax; strafe++) {
|
||||
for (int forward = forwardMin; forward <= forwardMax; forward++) {
|
||||
VectorData result = new VectorData(possibleLastTickOutput.vector.clone()
|
||||
VectorData result = new VectorData.MoveVectorData(possibleLastTickOutput.vector.clone()
|
||||
.add(getMovementResultFromInput(player, transformInputsToVector(player, new Vector(strafe, 0, forward)), speed, player.xRot)),
|
||||
possibleLastTickOutput, VectorData.VectorType.InputResult);
|
||||
possibleLastTickOutput, VectorData.VectorType.InputResult, forward, strafe);
|
||||
result = result.returnNewModified(result.vector.clone().multiply(player.stuckSpeedMultiplier), VectorData.VectorType.StuckMultiplier);
|
||||
result = result.returnNewModified(handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
|
||||
// Signal that we need to flip sneaking bounding box
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ public class PredictionEngineNormal extends PredictionEngine {
|
||||
// And 0.03 didn't affect onGround status
|
||||
// The player cannot jump
|
||||
final OptionalInt jumpBoost = player.compensatedEntities.getPotionLevelForPlayer(PotionTypes.JUMP_BOOST);
|
||||
if (((jumpBoost.isEmpty() || jumpBoost.getAsInt() >= 0) && player.onGround) || !player.lastOnGround)
|
||||
if (((!jumpBoost.isPresent() || jumpBoost.getAsInt() >= 0) && player.onGround) || !player.lastOnGround)
|
||||
return;
|
||||
|
||||
JumpPower.jumpFromGround(player, jump);
|
||||
|
||||
+5
-2
@@ -22,9 +22,12 @@ import java.util.Set;
|
||||
public final class PredictionEngineRideableUtils {
|
||||
|
||||
public static Set<VectorData> handleJumps(GrimPlayer player, Set<VectorData> possibleVectors) {
|
||||
if (!(player.compensatedEntities.self.getRiding() instanceof PacketEntityHorse horse)) return possibleVectors;
|
||||
if (!(player.compensatedEntities.self.getRiding() instanceof PacketEntityHorse)) return possibleVectors;
|
||||
|
||||
if (horse instanceof PacketEntityCamel camel) {
|
||||
PacketEntityHorse horse = (PacketEntityHorse) player.compensatedEntities.self.getRiding();
|
||||
|
||||
if (horse instanceof PacketEntityCamel) {
|
||||
PacketEntityCamel camel = (PacketEntityCamel) horse;
|
||||
handleCamelDash(player, possibleVectors, camel);
|
||||
} else {
|
||||
handleHorseJumping(player, possibleVectors, horse);
|
||||
|
||||
@@ -40,7 +40,12 @@ public class MessageUtil {
|
||||
|
||||
public @NotNull String replacePlaceholders(@Nullable Object object, @NotNull String string) {
|
||||
if (!hasPlaceholderAPI) return string;
|
||||
return PlaceholderAPI.setPlaceholders(object instanceof OfflinePlayer player ? player : null, string);
|
||||
|
||||
OfflinePlayer player = null;
|
||||
if (object instanceof OfflinePlayer) {
|
||||
player = (OfflinePlayer) object;
|
||||
}
|
||||
return PlaceholderAPI.setPlaceholders(player, string);
|
||||
}
|
||||
|
||||
public @NotNull Component replacePlaceholders(@NotNull GrimPlayer player, @NotNull Component component) {
|
||||
@@ -57,32 +62,33 @@ public class MessageUtil {
|
||||
|
||||
// hex codes
|
||||
Matcher matcher = HEX_PATTERN.matcher(string);
|
||||
StringBuilder sb = new StringBuilder(string.length());
|
||||
StringBuffer sb = new StringBuffer(string.length()); // this is synchronized but doesn't need to be; its easier for upstream compat though
|
||||
|
||||
while (matcher.find()) {
|
||||
matcher.appendReplacement(sb, "<#" + matcher.group(0).replaceAll("[&§#x]", "") + ">");
|
||||
}
|
||||
|
||||
string = matcher.appendTail(sb).toString();
|
||||
matcher.appendTail(sb);
|
||||
string = sb.toString();
|
||||
|
||||
// MiniMessage doesn't like legacy formatting codes
|
||||
string = ChatColor.translateAlternateColorCodes('&', string)
|
||||
.replace("§0", "<black>")
|
||||
.replace("§1", "<dark_blue>")
|
||||
.replace("§2", "<dark_green>")
|
||||
.replace("§3", "<dark_aqua>")
|
||||
.replace("§4", "<dark_red>")
|
||||
.replace("§5", "<dark_purple>")
|
||||
.replace("§6", "<gold>")
|
||||
.replace("§7", "<gray>")
|
||||
.replace("§8", "<dark_gray>")
|
||||
.replace("§9", "<blue>")
|
||||
.replace("§a", "<green>")
|
||||
.replace("§b", "<aqua>")
|
||||
.replace("§c", "<red>")
|
||||
.replace("§d", "<light_purple>")
|
||||
.replace("§e", "<yellow>")
|
||||
.replace("§f", "<white>")
|
||||
.replace("§0", "<!b><!i><!u><!st><!obf><black>")
|
||||
.replace("§1", "<!b><!i><!u><!st><!obf><dark_blue>")
|
||||
.replace("§2", "<!b><!i><!u><!st><!obf><dark_green>")
|
||||
.replace("§3", "<!b><!i><!u><!st><!obf><dark_aqua>")
|
||||
.replace("§4", "<!b><!i><!u><!st><!obf><dark_red>")
|
||||
.replace("§5", "<!b><!i><!u><!st><!obf><dark_purple>")
|
||||
.replace("§6", "<!b><!i><!u><!st><!obf><gold>")
|
||||
.replace("§7", "<!b><!i><!u><!st><!obf><gray>")
|
||||
.replace("§8", "<!b><!i><!u><!st><!obf><dark_gray>")
|
||||
.replace("§9", "<!b><!i><!u><!st><!obf><blue>")
|
||||
.replace("§a", "<!b><!i><!u><!st><!obf><green>")
|
||||
.replace("§b", "<!b><!i><!u><!st><!obf><aqua>")
|
||||
.replace("§c", "<!b><!i><!u><!st><!obf><red>")
|
||||
.replace("§d", "<!b><!i><!u><!st><!obf><light_purple>")
|
||||
.replace("§e", "<!b><!i><!u><!st><!obf><yellow>")
|
||||
.replace("§f", "<!b><!i><!u><!st><!obf><white>")
|
||||
.replace("§r", "<reset>")
|
||||
.replace("§k", "<obfuscated>")
|
||||
.replace("§l", "<bold>")
|
||||
|
||||
@@ -9,7 +9,7 @@ import ac.grim.grimac.utils.collisions.blocks.DoorHandler;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.ComplexCollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.HitData;
|
||||
import ac.grim.grimac.utils.data.BlockHitData;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
|
||||
import ac.grim.grimac.utils.latency.CompensatedWorld;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
@@ -46,7 +46,7 @@ public class BlockPlace {
|
||||
private static final BlockFace[] BY_2D = { BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST };
|
||||
static final BlockFace[] BY_3D = { BlockFace.DOWN, BlockFace.UP, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST };
|
||||
@Setter
|
||||
Vector3i blockPosition;
|
||||
public Vector3i blockPosition;
|
||||
@Getter
|
||||
InteractionHand hand;
|
||||
@Getter
|
||||
@@ -60,7 +60,7 @@ public class BlockPlace {
|
||||
@Getter
|
||||
StateType material;
|
||||
@Getter
|
||||
@Nullable HitData hitData;
|
||||
@Nullable BlockHitData hitData;
|
||||
@Getter
|
||||
int faceId;
|
||||
BlockFace face;
|
||||
@@ -76,7 +76,7 @@ public class BlockPlace {
|
||||
// Allocated once instead of in functions to reduce new[] calls that need to be made. Since per-instance BlockPlace is always dealt with on the same thread we can use 1 buffer array
|
||||
private final SimpleCollisionBox[] collisions = new SimpleCollisionBox[ComplexCollisionBox.DEFAULT_MAX_COLLISION_BOX_SIZE];
|
||||
|
||||
public BlockPlace(GrimPlayer player, InteractionHand hand, Vector3i blockPosition, int faceId, BlockFace face, ItemStack itemStack, HitData hitData) {
|
||||
public BlockPlace(GrimPlayer player, InteractionHand hand, Vector3i blockPosition, int faceId, BlockFace face, ItemStack itemStack, BlockHitData hitData) {
|
||||
this.player = player;
|
||||
this.hand = hand;
|
||||
this.blockPosition = blockPosition;
|
||||
@@ -540,14 +540,21 @@ public class BlockPlace {
|
||||
}
|
||||
|
||||
public Vector3i getNormalBlockFace() {
|
||||
return switch (face) {
|
||||
case DOWN -> new Vector3i(0, -1, 0);
|
||||
case SOUTH -> new Vector3i(0, 0, 1);
|
||||
case NORTH -> new Vector3i(0, 0, -1);
|
||||
case WEST -> new Vector3i(-1, 0, 0);
|
||||
case EAST -> new Vector3i(1, 0, 0);
|
||||
default -> new Vector3i(0, 1, 0);
|
||||
};
|
||||
switch (face) {
|
||||
default:
|
||||
case UP:
|
||||
return new Vector3i(0, 1, 0);
|
||||
case DOWN:
|
||||
return new Vector3i(0, -1, 0);
|
||||
case SOUTH:
|
||||
return new Vector3i(0, 0, 1);
|
||||
case NORTH:
|
||||
return new Vector3i(0, 0, -1);
|
||||
case WEST:
|
||||
return new Vector3i(-1, 0, 0);
|
||||
case EAST:
|
||||
return new Vector3i(1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void set(StateType material) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public enum BlockPlaceResult {
|
||||
|
||||
@@ -374,7 +375,7 @@ public enum BlockPlaceResult {
|
||||
place.set();
|
||||
}
|
||||
}, ItemTypes.values().stream().filter(mat -> mat.getName().getKey().contains("candle_cake"))
|
||||
.toList().toArray(new ItemType[0])),
|
||||
.collect(Collectors.toList()).toArray(new ItemType[0])),
|
||||
|
||||
PISTON_BASE((player, place) -> {
|
||||
WrappedBlockState piston = place.getMaterial().createBlockState(CompensatedWorld.blockVersion);
|
||||
|
||||
@@ -107,11 +107,17 @@ public class ConsumesBlockPlace {
|
||||
Attachment attachment = bell.getAttachment();
|
||||
BlockFace dir2 = BlockFace.valueOf(direction.name());
|
||||
|
||||
return switch (attachment) {
|
||||
case FLOOR -> AxisUtil.isSameAxis(dir, dir2);
|
||||
case SINGLE_WALL, DOUBLE_WALL -> !AxisUtil.isSameAxis(dir, dir2);
|
||||
case CEILING -> true;
|
||||
};
|
||||
switch (attachment) {
|
||||
case FLOOR:
|
||||
return AxisUtil.isSameAxis(dir, dir2);
|
||||
case SINGLE_WALL:
|
||||
case DOUBLE_WALL:
|
||||
return !AxisUtil.isSameAxis(dir, dir2);
|
||||
case CEILING:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,42 +18,64 @@ public class BlockFaceHelper {
|
||||
*/
|
||||
@Deprecated
|
||||
public static BlockFace fromBukkitFace(org.bukkit.block.BlockFace face) {
|
||||
return switch (face) {
|
||||
case NORTH -> BlockFace.NORTH;
|
||||
case SOUTH -> BlockFace.SOUTH;
|
||||
case WEST -> BlockFace.WEST;
|
||||
case EAST -> BlockFace.EAST;
|
||||
case UP -> BlockFace.UP;
|
||||
case DOWN -> BlockFace.DOWN;
|
||||
default -> BlockFace.OTHER;
|
||||
};
|
||||
switch (face) {
|
||||
case NORTH:
|
||||
return BlockFace.NORTH;
|
||||
case SOUTH:
|
||||
return BlockFace.SOUTH;
|
||||
case WEST:
|
||||
return BlockFace.WEST;
|
||||
case EAST:
|
||||
return BlockFace.EAST;
|
||||
case UP:
|
||||
return BlockFace.UP;
|
||||
case DOWN:
|
||||
return BlockFace.DOWN;
|
||||
default:
|
||||
return BlockFace.OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
public static BlockFace getClockWise(BlockFace face) {
|
||||
return switch (face) {
|
||||
case NORTH -> BlockFace.EAST;
|
||||
case SOUTH -> BlockFace.WEST;
|
||||
case WEST -> BlockFace.NORTH;
|
||||
default -> BlockFace.SOUTH;
|
||||
};
|
||||
switch (face) {
|
||||
case NORTH:
|
||||
return BlockFace.EAST;
|
||||
case SOUTH:
|
||||
return BlockFace.WEST;
|
||||
case WEST:
|
||||
return BlockFace.NORTH;
|
||||
case EAST:
|
||||
default:
|
||||
return BlockFace.SOUTH;
|
||||
}
|
||||
}
|
||||
|
||||
public static BlockFace getPEClockWise(BlockFace face) {
|
||||
return switch (face) {
|
||||
case NORTH -> BlockFace.EAST;
|
||||
case SOUTH -> BlockFace.WEST;
|
||||
case WEST -> BlockFace.NORTH;
|
||||
default -> BlockFace.SOUTH;
|
||||
};
|
||||
switch (face) {
|
||||
case NORTH:
|
||||
return BlockFace.EAST;
|
||||
case SOUTH:
|
||||
return BlockFace.WEST;
|
||||
case WEST:
|
||||
return BlockFace.NORTH;
|
||||
case EAST:
|
||||
default:
|
||||
return BlockFace.SOUTH;
|
||||
}
|
||||
}
|
||||
|
||||
public static BlockFace getCounterClockwise(BlockFace face) {
|
||||
return switch (face) {
|
||||
case NORTH -> BlockFace.WEST;
|
||||
case SOUTH -> BlockFace.EAST;
|
||||
case WEST -> BlockFace.SOUTH;
|
||||
default -> BlockFace.NORTH;
|
||||
};
|
||||
switch (face) {
|
||||
case NORTH:
|
||||
return BlockFace.WEST;
|
||||
case SOUTH:
|
||||
return BlockFace.EAST;
|
||||
case WEST:
|
||||
return BlockFace.SOUTH;
|
||||
case EAST:
|
||||
default:
|
||||
return BlockFace.NORTH;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector offset(Vector toOffset, BlockFace face) {
|
||||
|
||||
@@ -3,9 +3,42 @@ package ac.grim.grimac.utils.change;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
|
||||
public record BlockModification(WrappedBlockState oldBlockContents, WrappedBlockState newBlockContents,
|
||||
Vector3i location, int tick,
|
||||
Cause cause) {
|
||||
public class BlockModification {
|
||||
private final WrappedBlockState oldBlockContents;
|
||||
private final WrappedBlockState newBlockContents;
|
||||
private final Vector3i location;
|
||||
private final int tick;
|
||||
private final Cause cause;
|
||||
private final long lastTransasction = -1;
|
||||
|
||||
public BlockModification(WrappedBlockState oldBlockContents, WrappedBlockState newBlockContents,
|
||||
Vector3i location, int tick, Cause cause) {
|
||||
this.oldBlockContents = oldBlockContents;
|
||||
this.newBlockContents = newBlockContents;
|
||||
this.location = location;
|
||||
this.tick = tick;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public WrappedBlockState oldBlockContents() {
|
||||
return oldBlockContents;
|
||||
}
|
||||
|
||||
public WrappedBlockState newBlockContents() {
|
||||
return newBlockContents;
|
||||
}
|
||||
|
||||
public Vector3i location() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public int tick() {
|
||||
return tick;
|
||||
}
|
||||
|
||||
public Cause cause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@@ -19,6 +52,7 @@ public record BlockModification(WrappedBlockState oldBlockContents, WrappedBlock
|
||||
START_DIGGING,
|
||||
APPLY_BLOCK_CHANGES,
|
||||
HANDLE_NETTY_SYNC_TRANSACTION,
|
||||
FINISHED_DIGGING,
|
||||
OTHER
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,27 @@
|
||||
package ac.grim.grimac.utils.change;
|
||||
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Tracks block modifications made by a player over time.
|
||||
*/
|
||||
public class PlayerBlockHistory {
|
||||
private final ConcurrentLinkedDeque<BlockModification> blockHistory = new ConcurrentLinkedDeque<>();
|
||||
// TODO, figure out how its possible for this to CME!
|
||||
public final Deque<BlockModification> modificationQueue = new ConcurrentLinkedDeque<>();
|
||||
|
||||
/**
|
||||
* Adds a new block modification to the history.
|
||||
* @param modification The block modification to add
|
||||
*/
|
||||
public void add(BlockModification modification) {
|
||||
blockHistory.add(modification);
|
||||
modificationQueue.add(modification);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,8 +29,29 @@ public class PlayerBlockHistory {
|
||||
* @param filter Predicate to filter modifications
|
||||
* @return Filtered list of block modifications
|
||||
*/
|
||||
public Iterable<BlockModification> getRecentModifications(Predicate<BlockModification> filter) {
|
||||
return blockHistory.stream().filter(filter).toList();
|
||||
public List<BlockModification> getRecentModifications(Predicate<BlockModification> filter) {
|
||||
return modificationQueue.stream().filter(filter).collect(Collectors.toList()); // Java 8+ compatible
|
||||
}
|
||||
|
||||
public List<WrappedBlockState> getBlockStates(Predicate<BlockModification> filter) {
|
||||
return modificationQueue.stream()
|
||||
.filter(filter)
|
||||
.flatMap(mod -> Stream.of(mod.oldBlockContents(), mod.newBlockContents()))
|
||||
.collect(Collectors.toList());// Java 8+ compatible
|
||||
}
|
||||
|
||||
public List<WrappedBlockState> getPreviousBlockStates(Predicate<BlockModification> filter) {
|
||||
return modificationQueue.stream()
|
||||
.filter(filter)
|
||||
.map(BlockModification::oldBlockContents)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<WrappedBlockState> getResultingBlockStates(Predicate<BlockModification> filter) {
|
||||
return modificationQueue.stream()
|
||||
.filter(filter)
|
||||
.map(BlockModification::newBlockContents)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,16 +59,18 @@ public class PlayerBlockHistory {
|
||||
* @param maxTick The maximum tick age to keep
|
||||
*/
|
||||
public void cleanup(int maxTick) {
|
||||
while (!blockHistory.isEmpty() && maxTick - blockHistory.peekFirst().tick() > 0) {
|
||||
blockHistory.removeFirst();
|
||||
while (!modificationQueue.isEmpty() && maxTick - modificationQueue.peekFirst().tick() > 0) {
|
||||
modificationQueue.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the size of the block history
|
||||
public int size() {
|
||||
return blockHistory.size();
|
||||
return modificationQueue.size();
|
||||
}
|
||||
|
||||
// Clear all block modifications
|
||||
public void clear() {
|
||||
blockHistory.clear();
|
||||
modificationQueue.clear();
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,51 @@ package ac.grim.grimac.utils.chunks;
|
||||
|
||||
import com.github.retrooper.packetevents.protocol.world.chunk.BaseChunk;
|
||||
|
||||
public record Column(int x, int z, BaseChunk[] chunks, int transaction) {
|
||||
public class Column {
|
||||
private final int x;
|
||||
private final int z;
|
||||
private final BaseChunk[] chunks;
|
||||
private final int transaction;
|
||||
|
||||
// This ability was removed in 1.17 because of the extended world height
|
||||
// Therefore, the size of the chunks are ALWAYS 16!
|
||||
// Constructor
|
||||
public Column(int x, int z, BaseChunk[] chunks, int transaction) {
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
this.chunks = chunks;
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public int x() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int z() {
|
||||
return z;
|
||||
}
|
||||
|
||||
public BaseChunk[] chunks() {
|
||||
return chunks;
|
||||
}
|
||||
|
||||
public int transaction() {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
// Method to merge chunks
|
||||
public void mergeChunks(BaseChunk[] toMerge) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (toMerge[i] != null) chunks[i] = toMerge[i];
|
||||
if (toMerge[i] != null) {
|
||||
chunks[i] = toMerge[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"Column{x=%d, z=%d, chunks=%s, transaction=%d}",
|
||||
x, z, java.util.Arrays.toString(chunks), transaction
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -54,22 +54,36 @@ public enum AxisUtil {
|
||||
}
|
||||
|
||||
public static AxisSelect getAxis(BlockFace face) {
|
||||
return switch (face) {
|
||||
case EAST -> EAST.select;
|
||||
case WEST -> WEST.select;
|
||||
case NORTH -> NORTH.select;
|
||||
case SOUTH -> SOUTH.select;
|
||||
case UP -> UP.select;
|
||||
default -> DOWN.select;
|
||||
};
|
||||
switch (face) {
|
||||
case EAST:
|
||||
return EAST.select;
|
||||
case WEST:
|
||||
return WEST.select;
|
||||
case NORTH:
|
||||
return NORTH.select;
|
||||
case SOUTH:
|
||||
return SOUTH.select;
|
||||
case UP:
|
||||
return UP.select;
|
||||
case DOWN:
|
||||
default:
|
||||
return DOWN.select;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSameAxis(BlockFace one, BlockFace two) {
|
||||
return switch (one) {
|
||||
case WEST, EAST -> two == BlockFace.WEST || two == BlockFace.EAST;
|
||||
case NORTH, SOUTH -> two == BlockFace.NORTH || two == BlockFace.SOUTH;
|
||||
case UP, DOWN -> two == BlockFace.UP || two == BlockFace.DOWN;
|
||||
default -> false;
|
||||
};
|
||||
switch (one) {
|
||||
case WEST:
|
||||
case EAST:
|
||||
return two == BlockFace.WEST || two == BlockFace.EAST;
|
||||
case NORTH:
|
||||
case SOUTH:
|
||||
return two == BlockFace.NORTH || two == BlockFace.SOUTH;
|
||||
case UP:
|
||||
case DOWN:
|
||||
return two == BlockFace.UP || two == BlockFace.DOWN;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 180 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user