Skip to content

Commit e2466f7

Browse files
committed
Updated CheatDetector
1 parent 01ee9c3 commit e2466f7

File tree

1 file changed

+261
-9
lines changed

1 file changed

+261
-9
lines changed

src/main/java/net/wurstclient/hacks/CheatDetectorHack.java

Lines changed: 261 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)