package com.xeroth.xeroanticheat; import com.xeroth.xeroanticheat.checks.combat.*; import com.xeroth.xeroanticheat.checks.misc.*; import com.xeroth.xeroanticheat.checks.movement.*; import com.xeroth.xeroanticheat.check.Check; import com.xeroth.xeroanticheat.command.XACCommand; import com.xeroth.xeroanticheat.listener.CombatListener; import com.xeroth.xeroanticheat.listener.MiscListener; import com.xeroth.xeroanticheat.listener.MovementListener; import com.xeroth.xeroanticheat.manager.CheckManager; import com.xeroth.xeroanticheat.manager.ConfigManager; import com.xeroth.xeroanticheat.manager.DatabaseManager; import com.xeroth.xeroanticheat.manager.PunishmentManager; import com.xeroth.xeroanticheat.manager.ViolationManager; import com.xeroth.xeroanticheat.protocol.PacketListener; import com.xeroth.xeroanticheat.data.PlayerData; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffectType; import org.bukkit.plugin.java.JavaPlugin; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** * XeroAntiCheat - Lightweight, accurate anti-cheat for Paper 1.21.x * * This plugin provides comprehensive cheat detection including movement, * combat, and miscellaneous checks with minimal false positives. */ public final class XeroAntiCheat extends JavaPlugin { private static XeroAntiCheat instance; private ConfigManager configManager; private ViolationManager violationManager; private PunishmentManager punishmentManager; private CheckManager checkManager; private PacketListener packetListener; private DatabaseManager databaseManager; private boolean protocolLibLoaded = false; private org.bukkit.scheduler.BukkitTask decayTask; // Staff alert toggles private final Map alertToggles = new ConcurrentHashMap<>(); // Verbose targets (per-player debug output) private final Set verboseTargets = ConcurrentHashMap.newKeySet(); @Override public void onEnable() { instance = this; // Check for ProtocolLib checkProtocolLib(); // Initialize managers initializeManagers(); // Register listeners registerListeners(); // Register commands registerCommands(); // Register checks registerChecks(); // Start decay task startDecayTask(); // Start potion effect refresh task startPotionRefreshTask(); // Start TimerCheck blink detection task startTimerBlinkTask(); getLogger().info("XeroAntiCheat v" + getDescription().getVersion() + " enabled!"); if (protocolLibLoaded) { getLogger().info("ProtocolLib detected - packet-level checks enabled"); } else { getLogger().info("ProtocolLib not found - using event-based detection"); } } @Override public void onDisable() { // Cancel all tasks Bukkit.getScheduler().cancelTasks(this); // Save any pending data if (violationManager != null) { violationManager.saveAll(); } // Close database if (databaseManager != null) { databaseManager.close(); } getLogger().info("XeroAntiCheat disabled!"); } private void checkProtocolLib() { try { Class.forName("com.comphenix.protocol.ProtocolLibrary"); protocolLibLoaded = true; } catch (ClassNotFoundException e) { protocolLibLoaded = false; } } private void initializeManagers() { configManager = new ConfigManager(this); configManager.loadConfig(); violationManager = new ViolationManager(this); punishmentManager = new PunishmentManager(this, violationManager); checkManager = new CheckManager(this); databaseManager = new DatabaseManager(this); databaseManager.initialize(); } private void registerListeners() { getServer().getPluginManager().registerEvents(new MovementListener(this), this); getServer().getPluginManager().registerEvents(new CombatListener(this), this); getServer().getPluginManager().registerEvents(new MiscListener(this), this); if (protocolLibLoaded) { packetListener = new PacketListener(this); packetListener.register(); } } private void registerCommands() { getCommand("xac").setExecutor(new XACCommand(this)); } private void registerChecks() { // Movement checks checkManager.registerCheck(new SpeedCheck(this)); checkManager.registerCheck(new FlyCheck(this)); checkManager.registerCheck(new JesusCheck(this)); checkManager.registerCheck(new NoFallCheck(this)); checkManager.registerCheck(new TimerCheck(this)); checkManager.registerCheck(new SpiderCheck(this)); checkManager.registerCheck(new GlideCheck(this)); checkManager.registerCheck(new PhaseCheck(this)); // Combat checks checkManager.registerCheck(new KillAuraCheck(this)); checkManager.registerCheck(new ReachCheck(this)); checkManager.registerCheck(new CriticalCheck(this)); checkManager.registerCheck(new AutoClickerCheck(this)); // VelocityCheck requires ProtocolLib if (protocolLibLoaded) { checkManager.registerCheck(new VelocityCheck(this)); } // Misc checks checkManager.registerCheck(new FastPlaceCheck(this)); checkManager.registerCheck(new ScaffoldCheck(this)); checkManager.registerCheck(new FastEatCheck(this)); checkManager.registerCheck(new InventoryMoveCheck(this)); getLogger().info("Registered " + checkManager.getRegisteredChecks().size() + " checks"); } private void startDecayTask() { int interval = configManager.getInt("violation.decay_interval", 30) * 20; decayTask = Bukkit.getScheduler().runTaskTimerAsynchronously(this, () -> { violationManager.decayAll(); }, interval, interval); } private void startPotionRefreshTask() { Bukkit.getScheduler().runTaskTimer(this, () -> { for (Player player : Bukkit.getOnlinePlayers()) { PlayerData data = violationManager.getPlayerData(player); if (data == null) continue; data.setHasSpeedEffect(player.hasPotionEffect(PotionEffectType.SPEED)); data.setHasSlownessEffect(player.hasPotionEffect(PotionEffectType.SLOWNESS)); data.setHasLevitation(player.hasPotionEffect(PotionEffectType.LEVITATION)); data.setHasDolphinsGrace(player.hasPotionEffect(PotionEffectType.DOLPHINS_GRACE)); data.setHasJumpBoost(player.hasPotionEffect(PotionEffectType.JUMP_BOOST)); data.setHasSlowFalling(player.hasPotionEffect(PotionEffectType.SLOW_FALLING)); var speed = player.getPotionEffect(PotionEffectType.SPEED); data.setSpeedLevel(speed != null ? speed.getAmplifier() + 1 : 0); var slowness = player.getPotionEffect(PotionEffectType.SLOWNESS); data.setSlownessLevel(slowness != null ? slowness.getAmplifier() + 1 : 0); var jump = player.getPotionEffect(PotionEffectType.JUMP_BOOST); data.setJumpBoostLevel(jump != null ? jump.getAmplifier() + 1 : 0); } }, 10L, 10L); } private void startTimerBlinkTask() { Bukkit.getScheduler().runTaskTimer(this, () -> { long now = System.currentTimeMillis(); long threshold = configManager.getInt("checks.timer.blink_threshold_ms", 500); Check timerCheck = checkManager.getCheck("Timer"); for (Player player : Bukkit.getOnlinePlayers()) { if (timerCheck != null && timerCheck.isBypassed(player)) continue; PlayerData data = violationManager.getPlayerData(player); if (data == null || data.getLastMovePacketTime() == 0) continue; long gap = now - data.getLastMovePacketTime(); if (gap > threshold) { violationManager.addViolation(player, "Timer", 2.0); punishmentManager.evaluate(player, "Timer"); data.setLastMovePacketTime(now); } } }, 5L, 5L); } /** * Reload the plugin configuration and re-register checks */ public void reload() { configManager.loadConfig(); violationManager.clearAll(); violationManager.setDecayRate( configManager.getDouble("violation.decay_rate", 0.5)); if (decayTask != null) { decayTask.cancel(); } startDecayTask(); getLogger().info("Configuration reloaded!"); } // Getters public static XeroAntiCheat getInstance() { return instance; } public ConfigManager getConfigManager() { return configManager; } public ViolationManager getViolationManager() { return violationManager; } public PunishmentManager getPunishmentManager() { return punishmentManager; } public CheckManager getCheckManager() { return checkManager; } public DatabaseManager getDatabaseManager() { return databaseManager; } public boolean isProtocolLibLoaded() { return protocolLibLoaded; } public boolean isAlertsEnabled(java.util.UUID uuid) { return alertToggles.getOrDefault(uuid, true); } public void setAlertsEnabled(java.util.UUID uuid, boolean enabled) { alertToggles.put(uuid, enabled); } public boolean isVerboseTarget(UUID uuid) { return verboseTargets.contains(uuid); } public void toggleVerboseTarget(UUID uuid) { if (!verboseTargets.remove(uuid)) { verboseTargets.add(uuid); } } }