XeroAntiCheat v1.1.0 bug fixes

This commit is contained in:
2026-03-15 03:59:30 -03:00
parent 8190b39160
commit ac5a8e807b
7 changed files with 46 additions and 59 deletions

View File

@@ -1,2 +1,10 @@
# XeroAntiCheat # XeroAntiCheat
Lightweight, accurate anti-cheat for Paper 1.21.x
## Latest Updates (v1.1.0)
- **ReachCheck**: Now measures distance to entity bounding box center instead of feet. Eliminates false negatives when attacking tall entities (horses, iron golems, withers). Also switched from `distance()` to `distanceSquared()` comparison, removing a `Math.sqrt()` from the hot path.
- **AutoClickerCheck**: `checkPattern()` rewritten with zero-allocation two-pass iterator approach. Previously allocated two `ArrayList` objects on every combat click.
- **TimerCheck**: Removed redundant blink detection from `check()` method. Blink detection is fully handled by the 5-tick scheduled task. `setLastMovePacketTime()` retained to feed the task.
- **KillAuraCheck**: Removed unused `EntityEffect` import.

View File

@@ -6,7 +6,7 @@
<groupId>com.xeroth</groupId> <groupId>com.xeroth</groupId>
<artifactId>xeroanticheat</artifactId> <artifactId>xeroanticheat</artifactId>
<version>1.0.9</version> <version>1.1.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>XeroAntiCheat</name> <name>XeroAntiCheat</name>

View File

@@ -5,9 +5,6 @@ import com.xeroth.xeroanticheat.check.Check;
import com.xeroth.xeroanticheat.data.PlayerData; import com.xeroth.xeroanticheat.data.PlayerData;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
/** /**
* AutoClickerCheck - Tracks CPS (clicks per second) over a 1-second sliding window. * AutoClickerCheck - Tracks CPS (clicks per second) over a 1-second sliding window.
* *
@@ -54,35 +51,33 @@ public class AutoClickerCheck extends Check {
* Check click pattern for suspiciously low variance * Check click pattern for suspiciously low variance
*/ */
private void checkPattern(PlayerData data, Player player, double minVariance) { private void checkPattern(PlayerData data, Player player, double minVariance) {
List<Long> clicks = new ArrayList<>(data.getClickTimestamps()); if (data.getClickTimestamps().size() < 5) return;
if (clicks.size() < 5) return;
// Calculate intervals between clicks
List<Long> intervals = new ArrayList<>();
for (int i = 0; i < clicks.size() - 1; i++) {
intervals.add(clicks.get(i) - clicks.get(i + 1));
}
if (intervals.isEmpty()) return;
// Calculate mean
double sum = 0; double sum = 0;
for (Long interval : intervals) { int intervalCount = 0;
sum += interval; Long prev = null;
for (Long ts : data.getClickTimestamps()) {
if (prev != null) {
sum += (prev - ts);
intervalCount++;
}
prev = ts;
} }
double mean = sum / intervals.size(); if (intervalCount == 0) return;
double mean = sum / intervalCount;
// Calculate variance
double varianceSum = 0; double varianceSum = 0;
for (Long interval : intervals) { prev = null;
double diff = interval - mean; for (Long ts : data.getClickTimestamps()) {
varianceSum += diff * diff; if (prev != null) {
double diff = (prev - ts) - mean;
varianceSum += diff * diff;
}
prev = ts;
} }
double variance = varianceSum / intervals.size();
double stdDev = Math.sqrt(variance); double stdDev = Math.sqrt(varianceSum / intervalCount);
// If variance is too low, flag (too perfect)
if (stdDev < minVariance && data.getCPS() > 10) { if (stdDev < minVariance && data.getCPS() > 10) {
flag(data, player, 1.0); flag(data, player, 1.0);
} }

View File

@@ -3,7 +3,6 @@ package com.xeroth.xeroanticheat.checks.combat;
import com.xeroth.xeroanticheat.XeroAntiCheat; import com.xeroth.xeroanticheat.XeroAntiCheat;
import com.xeroth.xeroanticheat.check.Check; import com.xeroth.xeroanticheat.check.Check;
import com.xeroth.xeroanticheat.data.PlayerData; import com.xeroth.xeroanticheat.data.PlayerData;
import org.bukkit.EntityEffect;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;

View File

@@ -60,12 +60,20 @@ public class ReachCheck extends Check {
// Get player eye location // Get player eye location
Location eyeLoc = player.getEyeLocation(); Location eyeLoc = player.getEyeLocation();
// Get target location (center of entity hitbox) // Use center of entity bounding box, not feet position.
Location targetLoc = target.getLocation(); // getLocation() returns feet; bounding box center is more accurate for tall entities.
org.bukkit.util.BoundingBox bb = target.getBoundingBox();
double targetX = (bb.getMinX() + bb.getMaxX()) / 2.0;
double targetY = (bb.getMinY() + bb.getMaxY()) / 2.0;
double targetZ = (bb.getMinZ() + bb.getMaxZ()) / 2.0;
// Calculate 3D distance // Calculate 3D distance from player eye to entity center (no Location object needed)
double distance = eyeLoc.distance(targetLoc); double dx = eyeLoc.getX() - targetX;
double dy = eyeLoc.getY() - targetY;
double dz = eyeLoc.getZ() - targetZ;
double distanceSquared = dx * dx + dy * dy + dz * dz;
return distance > maxReach; double maxReachSquared = maxReach * maxReach;
return distanceSquared > maxReachSquared;
} }
} }

View File

@@ -32,7 +32,6 @@ public class TimerCheck extends Check {
// Get thresholds // Get thresholds
int maxPacketsPerSecond = getConfigInt("max_packets_per_second", 22); int maxPacketsPerSecond = getConfigInt("max_packets_per_second", 22);
long blinkThresholdMs = getConfigInt("blink_threshold_ms", 500);
// If ProtocolLib is active, packet counting is handled by PacketListener // If ProtocolLib is active, packet counting is handled by PacketListener
if (plugin.isProtocolLibLoaded()) { if (plugin.isProtocolLibLoaded()) {
@@ -57,28 +56,6 @@ public class TimerCheck extends Check {
flag(data, player, (data.getPacketsThisSecond() - maxPacketsPerSecond) * 0.5); flag(data, player, (data.getPacketsThisSecond() - maxPacketsPerSecond) * 0.5);
} }
// Check for blink (packet suppression)
long lastMoveTime = data.getLastMovePacketTime();
if (lastMoveTime > 0) {
long gap = now - lastMoveTime;
if (gap > blinkThresholdMs) {
// Check if player teleported during the gap
PlayerData.PositionSnapshot current = data.getLastPosition();
PlayerData.PositionSnapshot last = data.getSecondLastPosition();
if (current != null && last != null) {
double dx = current.x() - last.x();
double dy = current.y() - last.y();
double dz = current.z() - last.z();
double distance = Math.sqrt(dx*dx + dy*dy + dz*dz);
// If teleported more than 10 blocks, likely blink
if (distance > 10) {
flag(data, player, 2.0);
}
}
}
}
data.setLastMovePacketTime(now); data.setLastMovePacketTime(now);
} }
} }

View File

@@ -1,5 +1,5 @@
name: XeroAntiCheat name: XeroAntiCheat
version: 1.0.9 version: 1.1.0
main: com.xeroth.xeroanticheat.XeroAntiCheat main: com.xeroth.xeroanticheat.XeroAntiCheat
author: Xeroth author: Xeroth
description: Lightweight, accurate anti-cheat for Paper 1.21.x description: Lightweight, accurate anti-cheat for Paper 1.21.x