Skip to content

Commit ff75a35

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents 352eeb9 + a48e196 commit ff75a35

37 files changed

Lines changed: 1068 additions & 449 deletions

File tree

anticheat/src/main/java/net/swofty/anticheat/engine/SwoftyPlayer.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public class SwoftyPlayer {
3232
private long lastPingResponse;
3333
private final FlagManager flagManager;
3434

35+
// Player ability state (from AbilitiesPacket)
36+
private boolean flying = false;
37+
private boolean allowFlight = false;
38+
private boolean creativeMode = false;
39+
3540
public SwoftyPlayer(UUID uuid) {
3641
this.uuid = uuid;
3742
this.flagManager = new FlagManager(uuid, new HashMap<>());
@@ -142,4 +147,14 @@ public void processMovement(@NotNull Pos packetPosition, boolean onGround) {
142147
public void sendPacket(SwoftyPacket packet) {
143148
SwoftyAnticheat.getLoader().sendPacket(uuid, packet);
144149
}
150+
151+
public void updateAbilities(boolean flying, boolean allowFlight, boolean creativeMode) {
152+
this.flying = flying;
153+
this.allowFlight = allowFlight;
154+
this.creativeMode = creativeMode;
155+
}
156+
157+
public boolean shouldBypassMovementChecks() {
158+
return flying || creativeMode || allowFlight;
159+
}
145160
}

anticheat/src/main/java/net/swofty/anticheat/event/SwoftyEventHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public static void callEvent(Object event) {
3232
try {
3333
entry.method.invoke(entry.instance, event);
3434
} catch (Exception e) {
35-
Logger.error(e, "Failed to invoke event listener method {} for event {}",
35+
Throwable cause = e.getCause() != null ? e.getCause() : e;
36+
Logger.error(cause, "Failed to invoke event listener method {} for event {}",
3637
entry.method.getName(), event.getClass().getSimpleName());
3738
}
3839
}

anticheat/src/main/java/net/swofty/anticheat/flag/FlagType.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ public enum FlagType {
1414
SPEED(SpeedFlag::new),
1515
FLIGHT(FlightFlag::new),
1616
TIMER(TimerFlag::new),
17-
STRAFE(StrafeFlag::new),
1817
PHASE(PhaseFlag::new),
1918
JESUS(JesusFlag::new),
2019

anticheat/src/main/java/net/swofty/anticheat/flag/flags/AimFlag.java

Lines changed: 2 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -31,75 +31,12 @@ void addRotation(float yawChange, float pitchChange) {
3131

3232
@ListenerMethod
3333
public void onPlayerPositionUpdate(PlayerPositionUpdateEvent event) {
34-
if (event.getPreviousTick() == null) return;
35-
36-
Pos currentPos = event.getCurrentTick().getPos();
37-
Pos previousPos = event.getPreviousTick().getPos();
38-
39-
float yawChange = Math.abs(currentPos.yaw() - previousPos.yaw());
40-
float pitchChange = Math.abs(currentPos.pitch() - previousPos.pitch());
41-
42-
// Normalize yaw change (handle wrapping)
43-
if (yawChange > 180) {
44-
yawChange = 360 - yawChange;
45-
}
46-
47-
UUID uuid = event.getPlayer().getUuid();
48-
RotationData data = rotationData.computeIfAbsent(uuid, k -> new RotationData());
49-
data.addRotation(yawChange, pitchChange);
50-
51-
if (data.yawChanges.size() < 10) return;
52-
53-
// Pattern 1: Impossible rotation speed (>180 degrees per tick is suspicious)
54-
if (yawChange > 180 || pitchChange > 90) {
55-
double certainty = Math.min(0.9, 0.6 + Math.max(yawChange, pitchChange) / 360.0);
56-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.AIM, certainty);
57-
}
58-
59-
// Pattern 2: Perfect lock-on (no micro-adjustments)
60-
// Human aim has small variations, aimbots lock perfectly
61-
if (isStaticAim(data.yawChanges) && isStaticAim(data.pitchChanges)) {
62-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.AIM, 0.75);
63-
}
64-
65-
// Pattern 3: Robotic patterns (same rotation amount repeatedly)
66-
if (hasRoboticPattern(data.yawChanges) || hasRoboticPattern(data.pitchChanges)) {
67-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.AIM, 0.8);
68-
}
34+
// Disabled: heuristic-based detection has too many false positives
6935
}
7036

7137
@ListenerMethod
7238
public void onPlayerAttack(PlayerAttackEvent event) {
73-
// Check if player snapped to target
74-
Pos attackerPos = event.getAttacker().getCurrentTick().getPos();
75-
Pos targetPos = event.getTargetPosition();
76-
77-
// Calculate required look angle to hit target
78-
Pos requiredLook = attackerPos.withLookAt(targetPos);
79-
80-
// Check how close the player's actual look is to perfect
81-
float yawDiff = Math.abs(attackerPos.yaw() - requiredLook.yaw());
82-
float pitchDiff = Math.abs(attackerPos.pitch() - requiredLook.pitch());
83-
84-
// Normalize yaw difference
85-
if (yawDiff > 180) yawDiff = 360 - yawDiff;
86-
87-
// Perfect aim (within 0.5 degrees) every time is suspicious
88-
if (yawDiff < 0.5 && pitchDiff < 0.5) {
89-
UUID uuid = event.getAttacker().getUuid();
90-
RotationData data = rotationData.get(uuid);
91-
92-
if (data != null && data.yawChanges.size() >= 5) {
93-
// Check if last few rotations were also perfect
94-
long perfectCount = data.yawChanges.stream()
95-
.filter(change -> change < 1.0)
96-
.count();
97-
98-
if (perfectCount >= 3) {
99-
event.getAttacker().flag(net.swofty.anticheat.flag.FlagType.AIM, 0.85);
100-
}
101-
}
102-
}
39+
// Disabled: heuristic-based detection has too many false positives
10340
}
10441

10542
private boolean isStaticAim(List<Float> changes) {

anticheat/src/main/java/net/swofty/anticheat/flag/flags/BadPacketsFlag.java

Lines changed: 4 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -22,78 +22,21 @@ private static class PacketData {
2222

2323
@ListenerMethod
2424
public void onPlayerPositionUpdate(PlayerPositionUpdateEvent event) {
25-
UUID uuid = event.getPlayer().getUuid();
26-
PacketData data = packetData.computeIfAbsent(uuid, k -> new PacketData());
27-
28-
long currentTime = System.currentTimeMillis();
2925
Pos currentPos = event.getCurrentTick().getPos();
3026

31-
// Pattern 1: Packet spam (too many packets too quickly)
32-
if (data.lastPacketTime > 0) {
33-
long timeDiff = currentTime - data.lastPacketTime;
34-
35-
if (timeDiff < 10) { // Less than 10ms between packets
36-
data.packetBurst++;
37-
38-
if (data.packetBurst > 5) {
39-
double certainty = Math.min(0.9, 0.5 + data.packetBurst * 0.05);
40-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.BADPACKETS, certainty);
41-
data.packetBurst = 0; // Reset after flagging
42-
}
43-
} else if (timeDiff > 50) {
44-
// Reset burst counter if normal timing
45-
data.packetBurst = 0;
46-
}
47-
}
48-
49-
// Pattern 2: Duplicate position packets
50-
if (data.lastPosition != null) {
51-
if (isSamePosition(currentPos, data.lastPosition)) {
52-
data.duplicatePackets++;
53-
54-
if (data.duplicatePackets > 10) {
55-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.BADPACKETS, 0.7);
56-
data.duplicatePackets = 0;
57-
}
58-
} else {
59-
data.duplicatePackets = 0;
60-
}
61-
}
62-
63-
// Pattern 3: Invalid rotation values
27+
// Only check mathematically impossible values
6428
float pitch = currentPos.pitch();
6529
float yaw = currentPos.yaw();
6630

31+
// Invalid pitch (must be -90 to 90, anything else is impossible)
6732
if (pitch < -90 || pitch > 90) {
68-
// Invalid pitch (should be clamped to -90 to 90)
69-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.BADPACKETS, 0.95);
33+
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.BADPACKETS, 0.99);
7034
}
7135

36+
// NaN or Infinite values are impossible
7237
if (Float.isNaN(pitch) || Float.isNaN(yaw) || Float.isInfinite(pitch) || Float.isInfinite(yaw)) {
73-
// Invalid float values
7438
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.BADPACKETS, 0.99);
7539
}
76-
77-
// Pattern 4: Position teleportation (large instant movement)
78-
if (data.lastPosition != null && event.getPreviousTick() != null) {
79-
double distance = calculateDistance(currentPos, data.lastPosition);
80-
81-
// More than 10 blocks in one tick without proper velocity is suspicious
82-
// (Could be legitimate teleport, but packets should reflect that)
83-
if (distance > 10) {
84-
Vel vel = event.getCurrentTick().getVel();
85-
double velocityMagnitude = Math.sqrt(vel.x() * vel.x() + vel.y() * vel.y() + vel.z() * vel.z());
86-
87-
// If movement is large but velocity is small, packets are malformed
88-
if (velocityMagnitude < 1.0) {
89-
double certainty = Math.min(0.85, 0.4 + (distance / 50.0));
90-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.BADPACKETS, certainty);
91-
}
92-
}
93-
}
94-
95-
data.lastPacketTime = currentTime;
96-
data.lastPosition = currentPos;
9740
}
9841

9942
@ListenerMethod
Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package net.swofty.anticheat.flag.flags;
22

33
import net.swofty.anticheat.engine.PlayerTickInformation;
4+
import net.swofty.anticheat.engine.SwoftyPlayer;
45
import net.swofty.anticheat.event.ListenerMethod;
6+
import net.swofty.anticheat.event.events.AnticheatPacketEvent;
57
import net.swofty.anticheat.event.events.PlayerPositionUpdateEvent;
8+
import net.swofty.anticheat.event.packet.AbilitiesPacket;
69
import net.swofty.anticheat.flag.Flag;
10+
import net.swofty.anticheat.flag.FlagType;
711
import net.swofty.anticheat.math.Vel;
812

913
public class FlightFlag extends Flag {
@@ -13,53 +17,49 @@ public class FlightFlag extends Flag {
1317
private static final double DRAG = 0.98; // Air resistance
1418

1519
@ListenerMethod
16-
public void onPlayerPositionUpdate(PlayerPositionUpdateEvent event) {
17-
PlayerTickInformation currentTick = event.getCurrentTick();
18-
PlayerTickInformation previousTick = event.getPreviousTick();
19-
20-
if (previousTick == null) return;
21-
22-
boolean onGround = currentTick.isOnGround();
23-
boolean wasOnGround = previousTick.isOnGround();
24-
25-
Vel currentVel = currentTick.getVel();
26-
Vel previousVel = previousTick.getVel();
27-
28-
double currentY = currentVel.y();
29-
double previousY = previousVel.y();
30-
31-
// Pattern 1: Hovering (staying at same Y without being on ground)
32-
if (!onGround && Math.abs(currentY) < 0.01 && Math.abs(previousY) < 0.01) {
33-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.FLIGHT, 0.85);
34-
return;
20+
public void onPacket(AnticheatPacketEvent event) {
21+
if (event.getPacket() instanceof AbilitiesPacket abilities) {
22+
SwoftyPlayer player = SwoftyPlayer.players.get(abilities.getPlayer().getUuid());
23+
if (player != null) {
24+
player.updateAbilities(abilities.isFlying(), abilities.isAllowFlight(), abilities.isCreativeMode());
25+
}
3526
}
27+
}
28+
29+
@ListenerMethod
30+
public void onPlayerPositionUpdate(PlayerPositionUpdateEvent event) {
31+
SwoftyPlayer player = event.getPlayer();
3632

37-
// Pattern 2: Ascending without jump (not on ground, moving up without initial jump velocity)
38-
if (!onGround && !wasOnGround && currentY > 0 && currentY > previousY * DRAG) {
39-
// Velocity should be decreasing due to gravity, not increasing
40-
double certainty = Math.min(0.9, 0.6 + Math.abs(currentY - previousY * DRAG) * 5);
41-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.FLIGHT, certainty);
33+
// Skip checks for players with flight abilities
34+
if (player.shouldBypassMovementChecks()) {
4235
return;
4336
}
4437

45-
// Pattern 3: Ignoring gravity (velocity not decreasing as expected)
46-
if (!onGround && !wasOnGround) {
47-
// Expected Y velocity with gravity
48-
double expectedY = (previousY + GRAVITY) * DRAG;
49-
double difference = Math.abs(currentY - expectedY);
38+
Vel currentVel = event.getCurrentTick().getVel();
39+
boolean onGround = event.getCurrentTick().isOnGround();
5040

51-
// Allow some tolerance for server/client lag
52-
if (difference > 0.1) {
53-
double certainty = Math.min(0.95, 0.5 + difference * 3);
54-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.FLIGHT, certainty);
41+
// Check for impossible upward velocity when not on ground
42+
// Normal jump velocity is ~0.42, anything significantly higher is suspicious
43+
if (!onGround && currentVel.y() > MAX_VERTICAL_SPEED * 1.5) {
44+
int airTicks = countAirTicks(player.getLastTicks());
45+
// After a few ticks in the air, upward velocity should be impossible without flying
46+
if (airTicks > 5) {
47+
player.flag(FlagType.FLIGHT, 0.9);
5548
}
5649
}
5750

58-
// Pattern 4: Multiple ticks in air with no Y velocity change
59-
int airTicks = countAirTicks(event.getPlayer().getLastTicks());
60-
if (airTicks > 5 && Math.abs(currentY) < 0.05) {
61-
double certainty = Math.min(0.95, 0.4 + (airTicks * 0.05));
62-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.FLIGHT, certainty);
51+
// Check for sustained horizontal flight (no gravity effect)
52+
if (!onGround) {
53+
int airTicks = countAirTicks(player.getLastTicks());
54+
// If in the air for many ticks without falling, likely flying
55+
if (airTicks >= 15) {
56+
// Check if Y velocity is suspiciously stable (not affected by gravity)
57+
double avgYVel = calculateAverageYVelocity(player.getLastTicks(), 10);
58+
if (avgYVel > -0.01 && avgYVel < 0.01) {
59+
// Hovering in place - very suspicious
60+
player.flag(FlagType.FLIGHT, 0.85);
61+
}
62+
}
6363
}
6464
}
6565

@@ -74,4 +74,15 @@ private int countAirTicks(java.util.List<PlayerTickInformation> ticks) {
7474
}
7575
return count;
7676
}
77+
78+
private double calculateAverageYVelocity(java.util.List<PlayerTickInformation> ticks, int count) {
79+
if (ticks.isEmpty()) return 0;
80+
double sum = 0;
81+
int actualCount = 0;
82+
for (int i = ticks.size() - 1; i >= 0 && actualCount < count; i--) {
83+
sum += ticks.get(i).getVel().y();
84+
actualCount++;
85+
}
86+
return actualCount > 0 ? sum / actualCount : 0;
87+
}
7788
}

anticheat/src/main/java/net/swofty/anticheat/flag/flags/OnGroundSpoofFlag.java

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,56 +9,7 @@
99
public class OnGroundSpoofFlag extends Flag {
1010
@ListenerMethod
1111
public void onPlayerPositionUpdate(PlayerPositionUpdateEvent event) {
12-
boolean packetOnGround = event.getCurrentTick().isOnGround();
13-
Vel currentVel = event.getCurrentTick().getVel();
14-
Pos currentPosition = event.getCurrentTick().getPos();
15-
16-
// Determine if player is truly on ground by checking Y velocity and position
17-
boolean trueOnGround = false;
18-
19-
// Pattern 1: Y velocity near zero indicates on ground
20-
if (Math.abs(currentVel.y()) < 0.01) {
21-
trueOnGround = true;
22-
}
23-
24-
// Pattern 2: Check previous tick Y velocity pattern
25-
if (event.getPreviousTick() != null) {
26-
Vel previousVel = event.getPreviousTick().getVel();
27-
28-
// If Y velocity is consistent with being on ground (not falling/jumping)
29-
if (Math.abs(currentVel.y()) < 0.1 && Math.abs(previousVel.y()) < 0.5) {
30-
trueOnGround = true;
31-
}
32-
33-
// If player was falling and suddenly stopped, they're on ground
34-
if (previousVel.y() < -0.1 && Math.abs(currentVel.y()) < 0.01) {
35-
trueOnGround = true;
36-
}
37-
}
38-
39-
// Pattern 3: Check if Y position is on a block boundary
40-
double yFraction = currentPosition.y() - Math.floor(currentPosition.y());
41-
if (yFraction < 0.01 || yFraction > 0.99) {
42-
// Y position suggests standing on block
43-
trueOnGround = true;
44-
}
45-
46-
// Now compare packet claim vs reality
47-
if (packetOnGround && !trueOnGround) {
48-
// Client claims on ground but isn't
49-
// This is used by some fly hacks to avoid fall damage
50-
double certainty = 0.75;
51-
52-
// Higher certainty if player is high in the air
53-
if (Math.abs(currentVel.y()) > 0.3) {
54-
certainty = 0.9;
55-
}
56-
57-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.ON_GROUND_SPOOF, certainty);
58-
} else if (!packetOnGround && trueOnGround) {
59-
// Client claims not on ground but is
60-
// Less severe but still suspicious (could be used for some exploits)
61-
event.getPlayer().flag(net.swofty.anticheat.flag.FlagType.ON_GROUND_SPOOF, 0.6);
62-
}
12+
// Disabled: heuristic-based detection has too many false positives
13+
// Needs actual collision checking against world blocks to be accurate
6314
}
6415
}

0 commit comments

Comments
 (0)