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 playerDataCache = new ConcurrentHashMap<>(); private final MiniMessage miniMessage = MiniMessage.miniMessage(); private volatile double decayRate; public ViolationManager(XeroAntiCheat plugin) { this.plugin = plugin; this.decayRate = plugin.getConfigManager().getDouble("violation.decay_rate", 0.5); } public void setDecayRate(double rate) { this.decayRate = rate; } /** * 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())) { 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); } } 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() { 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; } }