Initial commit: XeroAntiCheat v1.0.7
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package com.xeroth.xeroanticheat.manager;
|
||||
|
||||
import com.xeroth.xeroanticheat.XeroAntiCheat;
|
||||
import com.xeroth.xeroanticheat.check.Check;
|
||||
import com.xeroth.xeroanticheat.data.PlayerData;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages registration and execution of all checks.
|
||||
*/
|
||||
public class CheckManager {
|
||||
|
||||
private final XeroAntiCheat plugin;
|
||||
|
||||
private final Map<String, Check> checksByName = new HashMap<>();
|
||||
private final List<Check> registeredChecks = new ArrayList<>();
|
||||
|
||||
public CheckManager(XeroAntiCheat plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a check
|
||||
*/
|
||||
public void registerCheck(Check check) {
|
||||
String name = check.getName().toLowerCase();
|
||||
checksByName.put(name, check);
|
||||
registeredChecks.add(check);
|
||||
plugin.getLogger().info("Registered check: " + check.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a check by name
|
||||
*/
|
||||
public Check getCheck(String name) {
|
||||
return checksByName.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a specific check
|
||||
*/
|
||||
public void runCheck(String checkName, PlayerData data, Player player) {
|
||||
Check check = checksByName.get(checkName.toLowerCase());
|
||||
if (check != null && check.isEnabled()) {
|
||||
check.check(data, player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered checks
|
||||
*/
|
||||
public List<Check> getRegisteredChecks() {
|
||||
return new ArrayList<>(registeredChecks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all enabled checks
|
||||
*/
|
||||
public List<Check> getEnabledChecks() {
|
||||
return registeredChecks.stream()
|
||||
.filter(Check::isEnabled)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload all checks (re-read config)
|
||||
*/
|
||||
public void reloadChecks() {
|
||||
for (Check check : registeredChecks) {
|
||||
// Re-check enabled state from config
|
||||
String path = "checks." + check.getName().toLowerCase() + ".enabled";
|
||||
boolean enabled = plugin.getConfigManager().getBoolean(path, true);
|
||||
check.setEnabled(enabled);
|
||||
}
|
||||
plugin.getLogger().info("Reloaded " + registeredChecks.size() + " checks");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get check by class name
|
||||
*/
|
||||
public Check getCheckByClass(Class<? extends Check> clazz) {
|
||||
for (Check check : registeredChecks) {
|
||||
if (check.getClass().equals(clazz)) {
|
||||
return check;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package com.xeroth.xeroanticheat.manager;
|
||||
|
||||
import com.xeroth.xeroanticheat.XeroAntiCheat;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Manages plugin configuration with validation and type-safe getters.
|
||||
*/
|
||||
public class ConfigManager {
|
||||
|
||||
private final XeroAntiCheat plugin;
|
||||
private FileConfiguration config;
|
||||
private File configFile;
|
||||
|
||||
private static final Map<String, Object> DEFAULTS = new HashMap<>();
|
||||
|
||||
static {
|
||||
// General
|
||||
DEFAULTS.put("enabled", true);
|
||||
DEFAULTS.put("debug", false);
|
||||
DEFAULTS.put("async_task_threads", 2);
|
||||
|
||||
// Violation
|
||||
DEFAULTS.put("violation.decay_interval", 30);
|
||||
DEFAULTS.put("violation.decay_rate", 0.5);
|
||||
|
||||
// Checks - Speed
|
||||
DEFAULTS.put("checks.speed.enabled", true);
|
||||
DEFAULTS.put("checks.speed.max_speed", 0.56);
|
||||
DEFAULTS.put("checks.speed.ping_factor", 1.0);
|
||||
DEFAULTS.put("checks.speed.buffer_ticks", 5);
|
||||
DEFAULTS.put("checks.speed.warn_vl", 10);
|
||||
DEFAULTS.put("checks.speed.kick_vl", 25);
|
||||
DEFAULTS.put("checks.speed.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.speed.permban_vl", 100);
|
||||
|
||||
// Checks - Fly
|
||||
DEFAULTS.put("checks.fly.enabled", true);
|
||||
DEFAULTS.put("checks.fly.fall_buffer", 10);
|
||||
DEFAULTS.put("checks.fly.ground_desync_threshold", 3);
|
||||
DEFAULTS.put("checks.fly.warn_vl", 10);
|
||||
DEFAULTS.put("checks.fly.kick_vl", 25);
|
||||
DEFAULTS.put("checks.fly.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.fly.permban_vl", 100);
|
||||
|
||||
// Checks - Jesus
|
||||
DEFAULTS.put("checks.jesus.enabled", true);
|
||||
DEFAULTS.put("checks.jesus.warn_vl", 10);
|
||||
DEFAULTS.put("checks.jesus.kick_vl", 25);
|
||||
DEFAULTS.put("checks.jesus.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.jesus.permban_vl", 100);
|
||||
|
||||
// Checks - NoFall
|
||||
DEFAULTS.put("checks.nofall.enabled", true);
|
||||
DEFAULTS.put("checks.nofall.min_fall_distance", 3);
|
||||
DEFAULTS.put("checks.nofall.warn_vl", 10);
|
||||
DEFAULTS.put("checks.nofall.kick_vl", 25);
|
||||
DEFAULTS.put("checks.nofall.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.nofall.permban_vl", 100);
|
||||
|
||||
// Checks - Timer
|
||||
DEFAULTS.put("checks.timer.enabled", true);
|
||||
DEFAULTS.put("checks.timer.max_packets_per_second", 22);
|
||||
DEFAULTS.put("checks.timer.blink_threshold_ms", 500);
|
||||
DEFAULTS.put("checks.timer.warn_vl", 10);
|
||||
DEFAULTS.put("checks.timer.kick_vl", 25);
|
||||
DEFAULTS.put("checks.timer.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.timer.permban_vl", 100);
|
||||
|
||||
// Checks - Spider
|
||||
DEFAULTS.put("checks.spider.enabled", true);
|
||||
DEFAULTS.put("checks.spider.warn_vl", 10);
|
||||
DEFAULTS.put("checks.spider.kick_vl", 25);
|
||||
DEFAULTS.put("checks.spider.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.spider.permban_vl", 100);
|
||||
|
||||
// Checks - Glide
|
||||
DEFAULTS.put("checks.glide.enabled", true);
|
||||
DEFAULTS.put("checks.glide.min_horizontal_speed", 0.5);
|
||||
DEFAULTS.put("checks.glide.max_y_decrease", 0.1);
|
||||
DEFAULTS.put("checks.glide.warn_vl", 10);
|
||||
DEFAULTS.put("checks.glide.kick_vl", 25);
|
||||
DEFAULTS.put("checks.glide.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.glide.permban_vl", 100);
|
||||
|
||||
// Checks - KillAura
|
||||
DEFAULTS.put("checks.killaura.enabled", true);
|
||||
DEFAULTS.put("checks.killaura.max_angle", 100);
|
||||
DEFAULTS.put("checks.killaura.max_rotation_change", 45);
|
||||
DEFAULTS.put("checks.killaura.multitarget_window_ms", 100);
|
||||
DEFAULTS.put("checks.killaura.warn_vl", 10);
|
||||
DEFAULTS.put("checks.killaura.kick_vl", 25);
|
||||
DEFAULTS.put("checks.killaura.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.killaura.permban_vl", 100);
|
||||
|
||||
// Checks - Reach
|
||||
DEFAULTS.put("checks.reach.enabled", true);
|
||||
DEFAULTS.put("checks.reach.max_reach", 3.2);
|
||||
DEFAULTS.put("checks.reach.creative_max_reach", 5.0);
|
||||
DEFAULTS.put("checks.reach.ping_factor", 1.0);
|
||||
DEFAULTS.put("checks.reach.warn_vl", 10);
|
||||
DEFAULTS.put("checks.reach.kick_vl", 25);
|
||||
DEFAULTS.put("checks.reach.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.reach.permban_vl", 100);
|
||||
|
||||
// Checks - Critical
|
||||
DEFAULTS.put("checks.critical.enabled", true);
|
||||
DEFAULTS.put("checks.critical.allow_jump_crits", true);
|
||||
DEFAULTS.put("checks.critical.warn_vl", 10);
|
||||
DEFAULTS.put("checks.critical.kick_vl", 25);
|
||||
DEFAULTS.put("checks.critical.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.critical.permban_vl", 100);
|
||||
|
||||
// Checks - AutoClicker
|
||||
DEFAULTS.put("checks.autoclicker.enabled", true);
|
||||
DEFAULTS.put("checks.autoclicker.max_cps", 20);
|
||||
DEFAULTS.put("checks.autoclicker.min_variance", 2.0);
|
||||
DEFAULTS.put("checks.autoclicker.warn_vl", 10);
|
||||
DEFAULTS.put("checks.autoclicker.kick_vl", 25);
|
||||
DEFAULTS.put("checks.autoclicker.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.autoclicker.permban_vl", 100);
|
||||
|
||||
// Checks - FastPlace
|
||||
DEFAULTS.put("checks.fastplace.enabled", true);
|
||||
DEFAULTS.put("checks.fastplace.max_blocks_per_second", 20);
|
||||
DEFAULTS.put("checks.fastplace.warn_vl", 10);
|
||||
DEFAULTS.put("checks.fastplace.kick_vl", 25);
|
||||
DEFAULTS.put("checks.fastplace.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.fastplace.permban_vl", 100);
|
||||
|
||||
// Checks - Scaffold
|
||||
DEFAULTS.put("checks.scaffold.enabled", true);
|
||||
DEFAULTS.put("checks.scaffold.min_pitch", 75);
|
||||
DEFAULTS.put("checks.scaffold.signals_required", 2);
|
||||
DEFAULTS.put("checks.scaffold.warn_vl", 10);
|
||||
DEFAULTS.put("checks.scaffold.kick_vl", 25);
|
||||
DEFAULTS.put("checks.scaffold.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.scaffold.permban_vl", 100);
|
||||
|
||||
// Checks - FastEat
|
||||
DEFAULTS.put("checks.fasteat.enabled", true);
|
||||
DEFAULTS.put("checks.fasteat.max_eat_ticks", 32);
|
||||
DEFAULTS.put("checks.fasteat.warn_vl", 10);
|
||||
DEFAULTS.put("checks.fasteat.kick_vl", 25);
|
||||
DEFAULTS.put("checks.fasteat.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.fasteat.permban_vl", 100);
|
||||
|
||||
// Checks - InventoryMove
|
||||
DEFAULTS.put("checks.inventorymove.enabled", true);
|
||||
DEFAULTS.put("checks.inventorymove.warn_vl", 10);
|
||||
DEFAULTS.put("checks.inventorymove.kick_vl", 25);
|
||||
DEFAULTS.put("checks.inventorymove.tempban_vl", 50);
|
||||
DEFAULTS.put("checks.inventorymove.permban_vl", 100);
|
||||
|
||||
// Punishments
|
||||
DEFAULTS.put("punishments.kick_command", "kick %player% &c[XAC] Illegal activity detected");
|
||||
DEFAULTS.put("punishments.tempban_command", "tempban %player% 30d %reason%");
|
||||
DEFAULTS.put("punishments.permban_command", "ban %player% %reason%");
|
||||
DEFAULTS.put("punishments.default_reason", "[XeroAntiCheat] Suspicious activity");
|
||||
|
||||
// Alerts
|
||||
DEFAULTS.put("alerts.enabled", true);
|
||||
DEFAULTS.put("alerts.format", "<dark_red>[<red>XAC<dark_red>] <white>%player% <red>failed <white>%check% <red>(VL: <white>%vl%<red>)");
|
||||
DEFAULTS.put("alerts.staff_format", "<gray>[%time%] %message%");
|
||||
|
||||
// Commands
|
||||
DEFAULTS.put("commands.reload_permission", "xac.admin");
|
||||
DEFAULTS.put("commands.bypass_permission", "xac.bypass");
|
||||
DEFAULTS.put("commands.alerts_permission", "xac.alerts");
|
||||
|
||||
// TPS
|
||||
DEFAULTS.put("tps.enabled", true);
|
||||
DEFAULTS.put("tps.min_tps_threshold", 18.0);
|
||||
}
|
||||
|
||||
public ConfigManager(XeroAntiCheat plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load or reload the configuration file
|
||||
*/
|
||||
public void loadConfig() {
|
||||
configFile = new File(plugin.getDataFolder(), "config.yml");
|
||||
|
||||
if (!configFile.exists()) {
|
||||
plugin.getDataFolder().mkdirs();
|
||||
try (InputStream in = plugin.getResource("config.yml")) {
|
||||
if (in != null) {
|
||||
Files.copy(in, configFile.toPath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Could not create default config", e);
|
||||
}
|
||||
}
|
||||
|
||||
config = YamlConfiguration.loadConfiguration(configFile);
|
||||
|
||||
// Apply defaults
|
||||
for (Map.Entry<String, Object> entry : DEFAULTS.entrySet()) {
|
||||
if (config.get(entry.getKey()) == null) {
|
||||
config.set(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Save if new keys were added
|
||||
try {
|
||||
config.save(configFile);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Could not save config", e);
|
||||
}
|
||||
|
||||
validateConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate configuration values
|
||||
*/
|
||||
private void validateConfig() {
|
||||
// Check for invalid values and log warnings
|
||||
if (config.getDouble("checks.speed.max_speed", 0.0) <= 0.0) {
|
||||
plugin.getLogger().warning("Invalid checks.speed.max_speed, using default 0.56");
|
||||
config.set("checks.speed.max_speed", 0.56);
|
||||
}
|
||||
|
||||
if (config.getInt("violation.decay_interval", 0) <= 0) {
|
||||
plugin.getLogger().warning("Invalid violation.decay_interval, using default 30");
|
||||
config.set("violation.decay_interval", 30);
|
||||
}
|
||||
}
|
||||
|
||||
// Type-safe getters
|
||||
|
||||
public boolean getBoolean(String path, boolean defaultValue) {
|
||||
return config.getBoolean(path, defaultValue);
|
||||
}
|
||||
|
||||
public boolean getBoolean(String path) {
|
||||
return config.getBoolean(path, DEFAULTS.containsKey(path) && DEFAULTS.get(path) instanceof Boolean
|
||||
? (Boolean) DEFAULTS.get(path) : false);
|
||||
}
|
||||
|
||||
public int getInt(String path, int defaultValue) {
|
||||
return config.getInt(path, defaultValue);
|
||||
}
|
||||
|
||||
public int getInt(String path) {
|
||||
return config.getInt(path, DEFAULTS.containsKey(path) && DEFAULTS.get(path) instanceof Integer
|
||||
? (Integer) DEFAULTS.get(path) : 0);
|
||||
}
|
||||
|
||||
public double getDouble(String path, double defaultValue) {
|
||||
return config.getDouble(path, defaultValue);
|
||||
}
|
||||
|
||||
public double getDouble(String path) {
|
||||
return config.getDouble(path, DEFAULTS.containsKey(path) && DEFAULTS.get(path) instanceof Double
|
||||
? (Double) DEFAULTS.get(path) : 0.0);
|
||||
}
|
||||
|
||||
public String getString(String path) {
|
||||
return config.getString(path, DEFAULTS.containsKey(path) ? String.valueOf(DEFAULTS.get(path)) : "");
|
||||
}
|
||||
|
||||
public String getString(String path, String defaultValue) {
|
||||
return config.getString(path, defaultValue);
|
||||
}
|
||||
|
||||
public FileConfiguration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return getBoolean("enabled", true);
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return getBoolean("debug", false);
|
||||
}
|
||||
|
||||
public String getAlertsFormat() {
|
||||
return getString("alerts.format");
|
||||
}
|
||||
|
||||
public boolean isAlertsEnabled() {
|
||||
return getBoolean("alerts.enabled", true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.xeroth.xeroanticheat.manager;
|
||||
|
||||
import com.xeroth.xeroanticheat.XeroAntiCheat;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class DatabaseManager {
|
||||
|
||||
private final XeroAntiCheat plugin;
|
||||
private Connection connection;
|
||||
|
||||
private static final String CREATE_TABLE = """
|
||||
CREATE TABLE IF NOT EXISTS punishments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
player_uuid TEXT NOT NULL,
|
||||
player_name TEXT NOT NULL,
|
||||
check_name TEXT NOT NULL,
|
||||
vl REAL NOT NULL
|
||||
)
|
||||
""";
|
||||
|
||||
private static final String INSERT = """
|
||||
INSERT INTO punishments (timestamp, type, player_uuid, player_name, check_name, vl)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
public DatabaseManager(XeroAntiCheat plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
try {
|
||||
File dbFile = new File(plugin.getDataFolder(), "data.db");
|
||||
plugin.getDataFolder().mkdirs();
|
||||
String url = "jdbc:sqlite:" + dbFile.getAbsolutePath();
|
||||
connection = DriverManager.getConnection(url);
|
||||
|
||||
try (var stmt = connection.createStatement()) {
|
||||
stmt.execute(CREATE_TABLE);
|
||||
}
|
||||
plugin.getLogger().info("SQLite database initialized.");
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to initialize SQLite database", e);
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void insertPunishment(
|
||||
String timestamp, String type,
|
||||
String playerUuid, String playerName,
|
||||
String checkName, double vl) {
|
||||
|
||||
if (connection == null) return;
|
||||
|
||||
try (PreparedStatement ps = connection.prepareStatement(INSERT)) {
|
||||
ps.setString(1, timestamp);
|
||||
ps.setString(2, type);
|
||||
ps.setString(3, playerUuid);
|
||||
ps.setString(4, playerName);
|
||||
ps.setString(5, checkName);
|
||||
ps.setDouble(6, vl);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to insert punishment record", e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void close() {
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to close SQLite connection", e);
|
||||
} finally {
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return connection != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package com.xeroth.xeroanticheat.manager;
|
||||
|
||||
import com.xeroth.xeroanticheat.XeroAntiCheat;
|
||||
import com.xeroth.xeroanticheat.check.Check;
|
||||
import com.xeroth.xeroanticheat.data.PlayerData;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Handles punishment execution and logging.
|
||||
*/
|
||||
public class PunishmentManager {
|
||||
|
||||
private final XeroAntiCheat plugin;
|
||||
private final ViolationManager violationManager;
|
||||
|
||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private File logsFolder;
|
||||
private File punishmentsLog;
|
||||
|
||||
public PunishmentManager(XeroAntiCheat plugin, ViolationManager violationManager) {
|
||||
this.plugin = plugin;
|
||||
this.violationManager = violationManager;
|
||||
initializeLogs();
|
||||
}
|
||||
|
||||
private void initializeLogs() {
|
||||
logsFolder = new File(plugin.getDataFolder(), "logs");
|
||||
if (!logsFolder.exists()) {
|
||||
logsFolder.mkdirs();
|
||||
}
|
||||
|
||||
punishmentsLog = new File(logsFolder, "punishments.log");
|
||||
if (!punishmentsLog.exists()) {
|
||||
try {
|
||||
punishmentsLog.createNewFile();
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Could not create punishments log", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate violations and apply appropriate punishment
|
||||
*/
|
||||
public void evaluate(Player player, String checkName) {
|
||||
if (!plugin.getConfigManager().isEnabled()) return;
|
||||
|
||||
PlayerData data = violationManager.getPlayerData(player);
|
||||
if (data == null) return;
|
||||
|
||||
double vl = data.getViolationLevel(checkName);
|
||||
|
||||
String checkPath = "checks." + checkName.toLowerCase() + ".";
|
||||
|
||||
int warnVl = plugin.getConfigManager().getInt(checkPath + "warn_vl", 10);
|
||||
int kickVl = plugin.getConfigManager().getInt(checkPath + "kick_vl", 25);
|
||||
int tempbanVl = plugin.getConfigManager().getInt(checkPath + "tempban_vl", 50);
|
||||
int permbanVl = plugin.getConfigManager().getInt(checkPath + "permban_vl", 100);
|
||||
|
||||
Check check = plugin.getCheckManager().getCheck(checkName);
|
||||
String category = check != null ? check.getCategory() : "misc";
|
||||
|
||||
sendAlert(player, checkName, vl, category);
|
||||
|
||||
if (vl >= permbanVl) {
|
||||
punish(player, checkName, "PERMBAN", permbanVl);
|
||||
} else if (vl >= tempbanVl) {
|
||||
punish(player, checkName, "TEMPBAN", tempbanVl);
|
||||
} else if (vl >= kickVl) {
|
||||
punish(player, checkName, "KICK", kickVl);
|
||||
} else if (vl >= warnVl) {
|
||||
warn(player, checkName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send warning to player
|
||||
*/
|
||||
private void warn(Player player, String checkName) {
|
||||
Component message = miniMessage.deserialize(
|
||||
"<red>Warning: <white>Suspicious behavior detected (" + checkName + ")"
|
||||
);
|
||||
player.sendMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply punishment based on type
|
||||
*/
|
||||
private void punish(Player player, String checkName, String type, double vl) {
|
||||
String reason = plugin.getConfigManager().getString("punishments.default_reason", "Suspicious activity");
|
||||
String playerName = player.getName();
|
||||
|
||||
switch (type) {
|
||||
case "KICK" -> {
|
||||
String kickCmd = plugin.getConfigManager().getString("punishments.kick_command",
|
||||
"kick %player% &c[XAC] Illegal activity detected");
|
||||
kickCmd = kickCmd.replace("%player%", playerName).replace("%reason%", reason);
|
||||
executeCommand(kickCmd);
|
||||
logPunishment(type, player, checkName, vl);
|
||||
}
|
||||
case "TEMPBAN" -> {
|
||||
String tempbanCmd = plugin.getConfigManager().getString("punishments.tempban_command",
|
||||
"tempban %player% 30d %reason%");
|
||||
tempbanCmd = tempbanCmd.replace("%player%", playerName).replace("%reason%", reason);
|
||||
executeCommand(tempbanCmd);
|
||||
logPunishment(type, player, checkName, vl);
|
||||
}
|
||||
case "PERMBAN" -> {
|
||||
String permbanCmd = plugin.getConfigManager().getString("punishments.permban_command",
|
||||
"ban %player% %reason%");
|
||||
permbanCmd = permbanCmd.replace("%player%", playerName).replace("%reason%", reason);
|
||||
executeCommand(permbanCmd);
|
||||
logPunishment(type, player, checkName, vl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a console command
|
||||
*/
|
||||
private void executeCommand(String command) {
|
||||
final String cmd = command.startsWith("/") ? command.substring(1) : command;
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send alert to staff members
|
||||
*/
|
||||
public void sendAlert(Player player, String checkName, double vl, String category) {
|
||||
if (!plugin.getConfigManager().isAlertsEnabled()) return;
|
||||
|
||||
String format = plugin.getConfigManager().getAlertsFormat();
|
||||
format = format.replace("%player%", player.getName())
|
||||
.replace("%check%", checkName)
|
||||
.replace("%vl%", String.valueOf((int) vl))
|
||||
.replace("%category%", category);
|
||||
|
||||
Component message = miniMessage.deserialize(format);
|
||||
|
||||
String categoryPerm = "xac.alerts." + category;
|
||||
|
||||
for (Player staff : Bukkit.getOnlinePlayers()) {
|
||||
boolean hasPermission = staff.hasPermission("xac.alerts")
|
||||
|| staff.hasPermission("xac.admin")
|
||||
|| staff.hasPermission(categoryPerm);
|
||||
if (!hasPermission) continue;
|
||||
if (!plugin.isAlertsEnabled(staff.getUniqueId())) continue;
|
||||
staff.sendMessage(message);
|
||||
}
|
||||
|
||||
plugin.getLogger().info(
|
||||
player.getName() + " failed " + checkName
|
||||
+ " [" + category + "] (VL: " + (int) vl + ")"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log punishment to file and database
|
||||
*/
|
||||
private void logPunishment(String type, Player player, String checkName, double vl) {
|
||||
final String timestamp = dateFormat.format(new Date());
|
||||
final String playerName = player.getName();
|
||||
final String playerUuid = player.getUniqueId().toString();
|
||||
|
||||
final String logLine = String.format("[%s] %s | %s | %s | %s | %.1f",
|
||||
timestamp,
|
||||
type,
|
||||
playerName,
|
||||
playerUuid,
|
||||
checkName,
|
||||
vl);
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try (FileWriter fw = new FileWriter(punishmentsLog, true);
|
||||
PrintWriter pw = new PrintWriter(fw)) {
|
||||
pw.println(logLine);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.WARNING, "Could not write to punishments log", e);
|
||||
}
|
||||
|
||||
DatabaseManager db = plugin.getDatabaseManager();
|
||||
if (db != null && db.isAvailable()) {
|
||||
db.insertPunishment(timestamp, type, playerUuid, playerName, checkName, vl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually punish a player for a check
|
||||
*/
|
||||
public void manualPunish(Player player, String checkName) {
|
||||
PlayerData data = violationManager.getPlayerData(player);
|
||||
if (data == null) return;
|
||||
|
||||
// Set VL to kick threshold and evaluate
|
||||
data.setViolationLevel(checkName, plugin.getConfigManager().getInt("checks." + checkName.toLowerCase() + ".kick_vl", 25));
|
||||
evaluate(player, checkName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.xeroth.xeroanticheat.manager;
|
||||
|
||||
import com.xeroth.xeroanticheat.XeroAntiCheat;
|
||||
import com.xeroth.xeroanticheat.data.PlayerData;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Manages violation levels for all players and checks.
|
||||
* Thread-safe implementation with temporal decay support.
|
||||
*/
|
||||
public class ViolationManager {
|
||||
|
||||
private final XeroAntiCheat plugin;
|
||||
|
||||
private final Map<UUID, PlayerData> playerDataCache = new ConcurrentHashMap<>();
|
||||
private final MiniMessage miniMessage = MiniMessage.miniMessage();
|
||||
|
||||
private double decayRate;
|
||||
|
||||
public ViolationManager(XeroAntiCheat plugin) {
|
||||
this.plugin = plugin;
|
||||
this.decayRate = plugin.getConfigManager().getDouble("violation.decay_rate", 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create player data for a player
|
||||
*/
|
||||
public PlayerData getPlayerData(Player player) {
|
||||
return playerDataCache.computeIfAbsent(player.getUniqueId(), k -> new PlayerData(player));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get player data by UUID
|
||||
*/
|
||||
public PlayerData getPlayerData(UUID uuid) {
|
||||
return playerDataCache.get(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add violation to a player for a specific check
|
||||
*/
|
||||
public void addViolation(Player player, String checkName, double weight) {
|
||||
PlayerData data = getPlayerData(player);
|
||||
if (data == null) return;
|
||||
|
||||
data.incrementViolation(checkName, weight);
|
||||
|
||||
data.setLastFlaggedCheck(checkName);
|
||||
data.setLastFlagTime(System.currentTimeMillis());
|
||||
|
||||
double newVl = data.getViolationLevel(checkName);
|
||||
|
||||
if (plugin.isVerboseTarget(player.getUniqueId())) {
|
||||
Component verbose = miniMessage.deserialize(
|
||||
"<gray>[<white>VERBOSE<gray>] <yellow>" + player.getName()
|
||||
+ " <gray>» <white>" + checkName
|
||||
+ " <gray>+<green>" + String.format("%.1f", weight)
|
||||
+ " <gray>(VL: <white>" + String.format("%.1f", newVl) + "<gray>)"
|
||||
);
|
||||
for (Player staff : Bukkit.getOnlinePlayers()) {
|
||||
if (staff.hasPermission("xac.command.verbose") || staff.hasPermission("xac.admin")) {
|
||||
staff.sendMessage(verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.getConfigManager().isDebug()) {
|
||||
plugin.getLogger().info(player.getName() + " violated " + checkName + " (VL: " + newVl + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get violation level for a player and check
|
||||
*/
|
||||
public double getViolationLevel(Player player, String checkName) {
|
||||
PlayerData data = getPlayerData(player);
|
||||
if (data == null) return 0.0;
|
||||
return data.getViolationLevel(checkName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decay all violation levels for all players
|
||||
*/
|
||||
public void decayAll() {
|
||||
decayRate = plugin.getConfigManager().getDouble("violation.decay_rate", 0.5);
|
||||
|
||||
for (PlayerData data : playerDataCache.values()) {
|
||||
for (String checkName : data.getViolationLevels().keySet()) {
|
||||
data.decayViolation(checkName, decayRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all violation levels for a player
|
||||
*/
|
||||
public void clearPlayer(UUID uuid) {
|
||||
playerDataCache.remove(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all violation levels for all players
|
||||
*/
|
||||
public void clearAll() {
|
||||
playerDataCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove player data on quit
|
||||
*/
|
||||
public void removePlayer(UUID uuid) {
|
||||
playerDataCache.remove(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all pending data (for shutdown)
|
||||
*/
|
||||
public void saveAll() {
|
||||
// Currently just clearing, but could be extended to persist to disk
|
||||
if (plugin.getConfigManager().isDebug()) {
|
||||
plugin.getLogger().info("Saved " + playerDataCache.size() + " player data records");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player has any violations above threshold
|
||||
*/
|
||||
public boolean hasViolations(Player player, double threshold) {
|
||||
PlayerData data = getPlayerData(player);
|
||||
if (data == null) return false;
|
||||
|
||||
for (double vl : data.getViolationLevels().values()) {
|
||||
if (vl >= threshold) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total violations for a player
|
||||
*/
|
||||
public double getTotalViolations(Player player) {
|
||||
PlayerData data = getPlayerData(player);
|
||||
if (data == null) return 0.0;
|
||||
|
||||
double total = 0.0;
|
||||
for (double vl : data.getViolationLevels().values()) {
|
||||
total += vl;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user