@@ -49,9 +49,78 @@ public final class CheatDetectorHack extends Hack implements UpdateListener
4949
5050 private final CheckboxSetting detectSpeed =
5151 new CheckboxSetting ("Detect speed" , true );
52+ // New: use more fine-grained speed configuration inspired by VelocityGuard
5253 private final SliderSetting speedThreshold =
53- new SliderSetting ("Speed threshold (blocks/s)" , 9 .0 , 5.0 , 40 .0 , 0.5 ,
54+ new SliderSetting ("Speed threshold (blocks/s)" , 10 .0 , 5.0 , 80 .0 , 0.1 ,
5455 ValueDisplay .DECIMAL );
56+ private final SliderSetting cancelDuration = new SliderSetting (
57+ "Cancel movement duration (s)" , 1 , 0 , 10 , 1 , ValueDisplay .INTEGER );
58+ private final CheckboxSetting latencyCompensationEnabled =
59+ new CheckboxSetting ("Latency compensation" , true );
60+ // latency compensation factors (multipliers for allowed speed)
61+ private final SliderSetting latencyVeryLowFactor = new SliderSetting (
62+ "very-low-ping factor" , 2.9 , 1.0 , 10.0 , 0.1 , ValueDisplay .DECIMAL );
63+ private final SliderSetting latencyLowFactor = new SliderSetting (
64+ "low-ping factor" , 2.9 , 1.0 , 10.0 , 0.1 , ValueDisplay .DECIMAL );
65+ private final SliderSetting latencyMediumFactor = new SliderSetting (
66+ "medium-ping factor" , 3.3 , 1.0 , 12.0 , 0.1 , ValueDisplay .DECIMAL );
67+ private final SliderSetting latencyHighFactor = new SliderSetting (
68+ "high-ping factor" , 3.6 , 1.0 , 15.0 , 0.1 , ValueDisplay .DECIMAL );
69+ private final SliderSetting latencyVeryHighFactor = new SliderSetting (
70+ "very-high-ping factor" , 4.6 , 1.0 , 20.0 , 0.1 , ValueDisplay .DECIMAL );
71+ private final SliderSetting latencyExtremeFactor = new SliderSetting (
72+ "extreme-ping factor" , 5.7 , 1.0 , 25.0 , 0.1 , ValueDisplay .DECIMAL );
73+ private final SliderSetting latencyUltraFactor = new SliderSetting (
74+ "ultra-ping factor" , 6.6 , 1.0 , 30.0 , 0.1 , ValueDisplay .DECIMAL );
75+ private final SliderSetting latencyInsaneFactor = new SliderSetting (
76+ "insane-ping factor" , 7.5 , 1.0 , 40.0 , 0.1 , ValueDisplay .DECIMAL );
77+
78+ // Burst tolerance - consecutive violations allowed before alert
79+ private final SliderSetting burstDefault = new SliderSetting (
80+ "burst-tolerance default" , 19 , 1 , 200 , 1 , ValueDisplay .INTEGER );
81+ private final SliderSetting burstVeryLow = new SliderSetting (
82+ "burst-tolerance very-low-ping" , 20 , 1 , 200 , 1 , ValueDisplay .INTEGER );
83+ private final SliderSetting burstLow = new SliderSetting (
84+ "burst-tolerance low-ping" , 21 , 1 , 200 , 1 , ValueDisplay .INTEGER );
85+ private final SliderSetting burstMedium = new SliderSetting (
86+ "burst-tolerance medium-ping" , 22 , 1 , 200 , 1 , ValueDisplay .INTEGER );
87+ private final SliderSetting burstHigh = new SliderSetting (
88+ "burst-tolerance high-ping" , 24 , 1 , 200 , 1 , ValueDisplay .INTEGER );
89+ private final SliderSetting burstVeryHigh = new SliderSetting (
90+ "burst-tolerance very-high-ping" , 27 , 1 , 200 , 1 , ValueDisplay .INTEGER );
91+ private final SliderSetting burstExtreme = new SliderSetting (
92+ "burst-tolerance extreme-ping" , 30 , 1 , 200 , 1 , ValueDisplay .INTEGER );
93+ private final SliderSetting burstUltra = new SliderSetting (
94+ "burst-tolerance ultra-ping" , 33 , 1 , 200 , 1 , ValueDisplay .INTEGER );
95+ private final SliderSetting burstInsane = new SliderSetting (
96+ "burst-tolerance insane-ping" , 35 , 1 , 200 , 1 , ValueDisplay .INTEGER );
97+
98+ // Knockback and special movement multipliers
99+ private final SliderSetting knockbackMultiplier = new SliderSetting (
100+ "knockback multiplier" , 6.0 , 1.0 , 20.0 , 0.1 , ValueDisplay .DECIMAL );
101+ private final SliderSetting knockbackDuration = new SliderSetting (
102+ "knockback duration (ms)" , 1000 , 0 , 10000 , 100 , ValueDisplay .INTEGER );
103+
104+ private final SliderSetting riptideMultiplier = new SliderSetting (
105+ "riptide multiplier" , 1.5 , 1.0 , 10.0 , 0.1 , ValueDisplay .DECIMAL );
106+ private final SliderSetting riptideDuration = new SliderSetting (
107+ "riptide duration (ms)" , 3000 , 0 , 10000 , 100 , ValueDisplay .INTEGER );
108+
109+ private final SliderSetting elytraGlidingMultiplier = new SliderSetting (
110+ "elytra gliding multiplier" , 1.5 , 1.0 , 10.0 , 0.1 , ValueDisplay .DECIMAL );
111+ private final SliderSetting elytraLandingDuration =
112+ new SliderSetting ("elytra landing duration (ms)" , 1500 , 0 , 10000 , 100 ,
113+ ValueDisplay .INTEGER );
114+
115+ private final SliderSetting vehicleSpeedMultiplier = new SliderSetting (
116+ "vehicle speed multiplier" , 1.9 , 1.0 , 10.0 , 0.1 , ValueDisplay .DECIMAL );
117+ private final SliderSetting vehicleIceSpeedMultiplier =
118+ new SliderSetting ("vehicle ice speed multiplier" , 4.3 , 1.0 , 20.0 , 0.1 ,
119+ ValueDisplay .DECIMAL );
120+
121+ // Extra buffer applied to all speed checks
122+ private final SliderSetting bufferMultiplier = new SliderSetting (
123+ "buffer multiplier" , 1.2 , 1.0 , 3.0 , 0.01 , ValueDisplay .DECIMAL );
55124
56125 private final CheckboxSetting detectFlight =
57126 new CheckboxSetting ("Detect flight" , true );
@@ -99,6 +168,34 @@ public CheatDetectorHack()
99168 setCategory (Category .OTHER );
100169 addSetting (detectSpeed );
101170 addSetting (speedThreshold );
171+ addSetting (cancelDuration );
172+ addSetting (latencyCompensationEnabled );
173+ addSetting (latencyVeryLowFactor );
174+ addSetting (latencyLowFactor );
175+ addSetting (latencyMediumFactor );
176+ addSetting (latencyHighFactor );
177+ addSetting (latencyVeryHighFactor );
178+ addSetting (latencyExtremeFactor );
179+ addSetting (latencyUltraFactor );
180+ addSetting (latencyInsaneFactor );
181+ addSetting (burstDefault );
182+ addSetting (burstVeryLow );
183+ addSetting (burstLow );
184+ addSetting (burstMedium );
185+ addSetting (burstHigh );
186+ addSetting (burstVeryHigh );
187+ addSetting (burstExtreme );
188+ addSetting (burstUltra );
189+ addSetting (burstInsane );
190+ addSetting (knockbackMultiplier );
191+ addSetting (knockbackDuration );
192+ addSetting (riptideMultiplier );
193+ addSetting (riptideDuration );
194+ addSetting (elytraGlidingMultiplier );
195+ addSetting (elytraLandingDuration );
196+ addSetting (vehicleSpeedMultiplier );
197+ addSetting (vehicleIceSpeedMultiplier );
198+ addSetting (bufferMultiplier );
102199 addSetting (detectFlight );
103200 addSetting (flightAirTicks );
104201 addSetting (flightClearanceThreshold );
@@ -217,24 +314,75 @@ private void checkSpeed(PlayerEntity player, PlayerStats stats,
217314 if (!detectSpeed .isChecked ())
218315 return ;
219316
220- if ( isUsingElytra ( player ) || player . hasVehicle ()
221- || player .isTouchingWater () || player .isSwimming ())
317+ // ignore if elytra or in vehicle or swimming - handle separately
318+ if ( player .isTouchingWater () || player .isSwimming ())
222319 return ;
223320
224- if (player .hasStatusEffect (StatusEffects .SPEED )
225- && horizontalPerSecond <= speedThreshold .getValue () * 1.2 )
226- return ;
321+ // compute base allowed speed with buffer
322+ double allowed =
323+ speedThreshold .getValue () * bufferMultiplier .getValue ();
324+
325+ // adjust for elytra
326+ if (isUsingElytra (player ))
327+ allowed *= elytraGlidingMultiplier .getValue ();
227328
228- if (horizontalPerSecond <= speedThreshold .getValue ())
329+ // adjust for vehicles
330+ Entity vehicle = player .getVehicle ();
331+ if (vehicle != null )
332+ {
333+ allowed *= vehicleSpeedMultiplier .getValue ();
334+ // boats on ice can be much faster - rudimentary check: block under
335+ // vehicle
336+ try
337+ {
338+ int bx = MathHelper .floor (vehicle .getX ());
339+ int bz = MathHelper .floor (vehicle .getZ ());
340+ int by = MathHelper .floor (vehicle .getY ()) - 1 ;
341+ BlockState under = ((ClientWorld )vehicle .getEntityWorld ())
342+ .getBlockState (new BlockPos (bx , by , bz ));
343+ String id =
344+ under .getBlock ().toString ().toLowerCase (Locale .ROOT );
345+ if (id .contains ("ice" ))
346+ allowed *= vehicleIceSpeedMultiplier .getValue ();
347+ }catch (Exception ignore )
348+ {}
349+ }
350+
351+ // status effect speed gives small allowance
352+ if (player .hasStatusEffect (StatusEffects .SPEED ))
353+ allowed *= 1.2 ;
354+
355+ // latency compensation
356+ int ping = getPlayerPing (player );
357+ if (latencyCompensationEnabled .isChecked () && ping >= 0 )
358+ allowed *= getLatencyFactorForPing (ping );
359+
360+ // final check
361+ if (horizontalPerSecond <= allowed )
362+ {
363+ // reset violation counter
364+ stats .speedViolationCount = 0 ;
229365 return ;
366+ }
230367
368+ // increase violation counter
369+ stats .speedViolationCount ++;
370+
371+ int allowedBurst = getBurstToleranceForPing (ping );
372+ if (stats .speedViolationCount < allowedBurst )
373+ return ; // within burst tolerance
374+
231375 if (tickCounter - stats .lastSpeedAlertTick < ALERT_COOLDOWN_TICKS )
232376 return ;
233377
234378 stats .lastSpeedAlertTick = tickCounter ;
235379 sendAlert (player ,
236- String .format (Locale .ROOT , "suspected of speed (%s blocks/s)" ,
237- formatDouble (horizontalPerSecond )));
380+ String .format (Locale .ROOT ,
381+ "suspected of speed (%.1f blocks/s) [allowed %.1f]" ,
382+ horizontalPerSecond , allowed ));
383+
384+ // reset after alert
385+ stats .speedViolationCount = 0 ;
238386 }
239387
240388 private void updateFlightPattern (PlayerEntity player , PlayerStats stats ,
@@ -472,6 +620,107 @@ private String formatDouble(double value)
472620 return String .format (Locale .ROOT , "%.1f" , value );
473621 }
474622
623+ /**
624+ * Try to obtain the player's ping/latency in milliseconds. Returns -1 if
625+ * unavailable.
626+ */
627+ private int getPlayerPing (PlayerEntity player )
628+ {
629+ try
630+ {
631+ var handler = MC .getNetworkHandler ();
632+ if (handler == null )
633+ return -1 ;
634+ var entry = handler .getPlayerListEntry (player .getUuid ());
635+ if (entry == null )
636+ return -1 ;
637+
638+ // try common method names via reflection to be resilient across
639+ // mappings
640+ try
641+ {
642+ java .lang .reflect .Method m =
643+ entry .getClass ().getMethod ("getLatency" );
644+ Object o = m .invoke (entry );
645+ if (o instanceof Integer )
646+ return (Integer )o ;
647+ if (o instanceof Long )
648+ return ((Long )o ).intValue ();
649+ }catch (NoSuchMethodException ignored )
650+ {}
651+
652+ try
653+ {
654+ java .lang .reflect .Method m =
655+ entry .getClass ().getMethod ("getLatencyMs" );
656+ Object o = m .invoke (entry );
657+ if (o instanceof Integer )
658+ return (Integer )o ;
659+ if (o instanceof Long )
660+ return ((Long )o ).intValue ();
661+ }catch (NoSuchMethodException ignored )
662+ {}
663+
664+ // fallback: try field "latency" if present
665+ try
666+ {
667+ java .lang .reflect .Field f =
668+ entry .getClass ().getDeclaredField ("latency" );
669+ f .setAccessible (true );
670+ Object o = f .get (entry );
671+ if (o instanceof Integer )
672+ return (Integer )o ;
673+ if (o instanceof Long )
674+ return ((Long )o ).intValue ();
675+ }catch (NoSuchFieldException ignored )
676+ {}
677+ }catch (Throwable t )
678+ { /* ignore */ }
679+ return -1 ;
680+ }
681+
682+ private double getLatencyFactorForPing (int pingMs )
683+ {
684+ if (pingMs < 0 )
685+ return 1.0 ;
686+ if (pingMs <= 50 )
687+ return latencyVeryLowFactor .getValue ();
688+ if (pingMs <= 100 )
689+ return latencyLowFactor .getValue ();
690+ if (pingMs <= 200 )
691+ return latencyMediumFactor .getValue ();
692+ if (pingMs <= 300 )
693+ return latencyHighFactor .getValue ();
694+ if (pingMs <= 500 )
695+ return latencyVeryHighFactor .getValue ();
696+ if (pingMs <= 750 )
697+ return latencyExtremeFactor .getValue ();
698+ if (pingMs <= 1000 )
699+ return latencyUltraFactor .getValue ();
700+ return latencyInsaneFactor .getValue ();
701+ }
702+
703+ private int getBurstToleranceForPing (int pingMs )
704+ {
705+ if (pingMs < 0 )
706+ return (int )burstDefault .getValueI ();
707+ if (pingMs <= 50 )
708+ return (int )burstVeryLow .getValueI ();
709+ if (pingMs <= 100 )
710+ return (int )burstLow .getValueI ();
711+ if (pingMs <= 200 )
712+ return (int )burstMedium .getValueI ();
713+ if (pingMs <= 300 )
714+ return (int )burstHigh .getValueI ();
715+ if (pingMs <= 500 )
716+ return (int )burstVeryHigh .getValueI ();
717+ if (pingMs <= 750 )
718+ return (int )burstExtreme .getValueI ();
719+ if (pingMs <= 1000 )
720+ return (int )burstUltra .getValueI ();
721+ return (int )burstInsane .getValueI ();
722+ }
723+
475724 private double getClearanceAboveGround (Entity entity , int maxDepth )
476725 {
477726 ClientWorld world = (ClientWorld )entity .getEntityWorld ();
@@ -570,6 +819,8 @@ private static final class PlayerStats
570819 private long lastFlightAlertTick ;
571820 private long lastBoatAlertTick ;
572821 private long lastAuraAlertTick ;
822+ // consecutive speed violations
823+ private int speedViolationCount ;
573824
574825 private void resetPosition (PlayerEntity player )
575826 {
@@ -589,6 +840,7 @@ private void resetPosition(PlayerEntity player)
589840 swingIntervals .clear ();
590841 lastSwingTick = 0L ;
591842 lastSwingProgress = player .getHandSwingProgress (1.0F );
843+ speedViolationCount = 0 ;
592844 }
593845 }
594846
0 commit comments