diff --git a/pom.xml b/pom.xml
index ed15152..feae0eb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.xeroth
xeroanticheat
- 1.0.8
+ 1.0.9
jar
XeroAntiCheat
diff --git a/src/main/java/com/xeroth/xeroanticheat/checks/movement/FlyCheck.java b/src/main/java/com/xeroth/xeroanticheat/checks/movement/FlyCheck.java
index 53e9b91..5c51c4e 100644
--- a/src/main/java/com/xeroth/xeroanticheat/checks/movement/FlyCheck.java
+++ b/src/main/java/com/xeroth/xeroanticheat/checks/movement/FlyCheck.java
@@ -67,8 +67,7 @@ public class FlyCheck extends Check {
// If moving up or staying at same height while not supposed to
if (velocity.getY() > 0.1 || Math.abs(velocity.getY()) < 0.01) {
if (data.getAirTicks() > fallBuffer) {
- // Additional check: see if player has jump boost
- if (!data.hasJumpBoost() || data.getJumpBoostLevel() <= 1) {
+ if (!data.hasJumpBoost()) {
flag(data, player);
}
}
diff --git a/src/main/java/com/xeroth/xeroanticheat/checks/movement/SpiderCheck.java b/src/main/java/com/xeroth/xeroanticheat/checks/movement/SpiderCheck.java
index 18224f0..fb9ac8a 100644
--- a/src/main/java/com/xeroth/xeroanticheat/checks/movement/SpiderCheck.java
+++ b/src/main/java/com/xeroth/xeroanticheat/checks/movement/SpiderCheck.java
@@ -48,9 +48,10 @@ public class SpiderCheck extends Check {
// Get blocks around player using block coordinates (no Location mutation)
org.bukkit.World world = player.getWorld();
- int blockX = player.getLocation().getBlockX();
- int blockY = player.getLocation().getBlockY();
- int blockZ = player.getLocation().getBlockZ();
+ org.bukkit.Location loc = player.getLocation();
+ int blockX = loc.getBlockX();
+ int blockY = loc.getBlockY();
+ int blockZ = loc.getBlockZ();
Material feetBlock = world.getBlockAt(blockX, blockY - 1, blockZ).getType();
Material bodyBlock = world.getBlockAt(blockX, blockY, blockZ).getType();
diff --git a/src/main/java/com/xeroth/xeroanticheat/data/PlayerData.java b/src/main/java/com/xeroth/xeroanticheat/data/PlayerData.java
index b641594..0308931 100644
--- a/src/main/java/com/xeroth/xeroanticheat/data/PlayerData.java
+++ b/src/main/java/com/xeroth/xeroanticheat/data/PlayerData.java
@@ -111,6 +111,9 @@ public class PlayerData {
// SpeedCheck tracking
private int speedViolationTicks = 0;
+ // Alert cooldown tracking
+ private final Map lastWarnTime = new ConcurrentHashMap<>();
+
public PlayerData(Player player) {
this.uuid = player.getUniqueId();
this.name = player.getName();
@@ -618,6 +621,14 @@ public class PlayerData {
this.speedViolationTicks = 0;
}
+ public long getLastWarnTime(String checkName) {
+ return lastWarnTime.getOrDefault(checkName, 0L);
+ }
+
+ public void setLastWarnTime(String checkName, long time) {
+ lastWarnTime.put(checkName, time);
+ }
+
public void clearPositionHistory() {
positionHistory.clear();
rotationHistory.clear();
diff --git a/src/main/java/com/xeroth/xeroanticheat/manager/ConfigManager.java b/src/main/java/com/xeroth/xeroanticheat/manager/ConfigManager.java
index 5f174eb..c020edf 100644
--- a/src/main/java/com/xeroth/xeroanticheat/manager/ConfigManager.java
+++ b/src/main/java/com/xeroth/xeroanticheat/manager/ConfigManager.java
@@ -171,6 +171,7 @@ public class ConfigManager {
DEFAULTS.put("alerts.enabled", true);
DEFAULTS.put("alerts.format", "[XAC] %player% failed %check% (VL: %vl%)");
DEFAULTS.put("alerts.staff_format", "[%time%] %message%");
+ DEFAULTS.put("alerts.cooldown_ms", 5000);
// Commands
DEFAULTS.put("commands.reload_permission", "xac.admin");
diff --git a/src/main/java/com/xeroth/xeroanticheat/manager/PunishmentManager.java b/src/main/java/com/xeroth/xeroanticheat/manager/PunishmentManager.java
index 37a17dc..3b12675 100644
--- a/src/main/java/com/xeroth/xeroanticheat/manager/PunishmentManager.java
+++ b/src/main/java/com/xeroth/xeroanticheat/manager/PunishmentManager.java
@@ -76,7 +76,17 @@ public class PunishmentManager {
Check check = plugin.getCheckManager().getCheck(checkName);
String category = check != null ? check.getCategory() : "misc";
- sendAlert(player, checkName, vl, category);
+ long cooldownMs = plugin.getConfigManager().getInt("alerts.cooldown_ms", 5000);
+ long now = System.currentTimeMillis();
+ long lastWarn = data.getLastWarnTime(checkName);
+ boolean cooledDown = (now - lastWarn) >= cooldownMs;
+
+ boolean isPunishment = vl >= kickVl;
+
+ if (cooledDown || isPunishment) {
+ sendAlert(player, checkName, vl, category);
+ data.setLastWarnTime(checkName, now);
+ }
if (vl >= permbanVl) {
punish(player, checkName, "PERMBAN", permbanVl);
@@ -84,7 +94,7 @@ public class PunishmentManager {
punish(player, checkName, "TEMPBAN", tempbanVl);
} else if (vl >= kickVl) {
punish(player, checkName, "KICK", kickVl);
- } else if (vl >= warnVl) {
+ } else if (vl >= warnVl && cooledDown) {
warn(player, checkName);
}
}
diff --git a/src/main/java/com/xeroth/xeroanticheat/manager/ViolationManager.java b/src/main/java/com/xeroth/xeroanticheat/manager/ViolationManager.java
index cc5fd55..638b28a 100644
--- a/src/main/java/com/xeroth/xeroanticheat/manager/ViolationManager.java
+++ b/src/main/java/com/xeroth/xeroanticheat/manager/ViolationManager.java
@@ -62,16 +62,21 @@ public class ViolationManager {
double newVl = data.getViolationLevel(checkName);
if (plugin.isVerboseTarget(player.getUniqueId())) {
- Component verbose = miniMessage.deserialize(
- "[VERBOSE] " + player.getName()
- + " » " + checkName
- + " +" + String.format("%.1f", weight)
- + " (VL: " + String.format("%.1f", newVl) + ")"
- );
- for (Player staff : Bukkit.getOnlinePlayers()) {
- if (staff.hasPermission("xac.command.verbose") || staff.hasPermission("xac.admin")) {
- staff.sendMessage(verbose);
+ long cooldownMs = plugin.getConfigManager().getInt("alerts.cooldown_ms", 5000);
+ long now = System.currentTimeMillis();
+ if ((now - data.getLastWarnTime(checkName)) >= cooldownMs) {
+ Component verbose = miniMessage.deserialize(
+ "[VERBOSE] " + player.getName()
+ + " » " + checkName
+ + " +" + String.format("%.1f", weight)
+ + " (VL: " + String.format("%.1f", newVl) + ")"
+ );
+ for (Player staff : Bukkit.getOnlinePlayers()) {
+ if (staff.hasPermission("xac.command.verbose") || staff.hasPermission("xac.admin")) {
+ staff.sendMessage(verbose);
+ }
}
+ data.setLastWarnTime(checkName, now);
}
}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 003bfea..bb14374 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -315,6 +315,11 @@ alerts:
# Staff-only alert format
staff_format: "[%time%] %message%"
+
+ # Minimum milliseconds between alert/warn messages for the same player+check.
+ # Prevents chat spam when a player is flagging at high frequency.
+ # Default: 5000ms (5 seconds). Set to 0 to disable throttling.
+ cooldown_ms: 5000
# ==========================================
# COMMANDS
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 2d173c3..c7507c2 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,5 +1,5 @@
name: XeroAntiCheat
-version: 1.0.8
+version: 1.0.9
main: com.xeroth.xeroanticheat.XeroAntiCheat
author: Xeroth
description: Lightweight, accurate anti-cheat for Paper 1.21.x