diff --git a/src/main/java/PlayerStatusSign.java b/src/main/java/PlayerStatusSign.java new file mode 100644 index 0000000..69b1a14 --- /dev/null +++ b/src/main/java/PlayerStatusSign.java @@ -0,0 +1,737 @@ +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Statistic; +import org.bukkit.block.Sign; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.*; + +public class PlayerStatusSign extends JavaPlugin implements Listener { + + private File signsFile; + private FileConfiguration signsConfig; + private File playersFile; + private FileConfiguration playersConfig; + private File configFile; + private FileConfiguration config; + + private Map signLocations; // Schildposition -> Spielername + private Map lastPlayerMovement; // Spieler UUID -> Zeitpunkt + private Map afkPlayers; // Spieler UUID -> AFK-Status + private Map fakeOfflinePlayers; // Spieler UUID -> Fake-Offline-Status + private Map originalGameModes; // Spieler UUID -> Ursprünglicher Gamemode + + private String prefix; + private String onlineColor; + private String afkColor; + private String offlineColor; + private String playerColor; + private String dateFormat; + private long afkTimeout; + private boolean afkChangeGamemode; + + private static final String CONFIG_VERSION = "1.0.6"; + private static final String PLUGIN_AUTHOR = "M_Viper"; + + // ------------------------------------------------------------ + // Lifecycle + // ------------------------------------------------------------ + @Override + public void onEnable() { + try { + getServer().getPluginManager().registerEvents(this, this); + + signLocations = new HashMap<>(); + lastPlayerMovement = new HashMap<>(); + afkPlayers = new HashMap<>(); + fakeOfflinePlayers = new HashMap<>(); + originalGameModes = new HashMap<>(); + + loadConfig(); + loadSigns(); + loadPlayers(); + startSignUpdater(); + + // Konsolenmeldung beim Plugin-Start + getLogger().info("[>] ========================================== [<]"); + getLogger().info(" PlayerStatusSign - " + getDescription().getVersion() + " (cfg " + CONFIG_VERSION + ")"); + getLogger().info(" Ersteller: " + PLUGIN_AUTHOR); + getLogger().info("[>] ========================================== [<]"); + } catch (Exception e) { + getLogger().severe("Fehler beim Laden des Plugins: " + e.getMessage()); + e.printStackTrace(); + getServer().getPluginManager().disablePlugin(this); + } + } + + @Override + public void onDisable() { + // Setze alle AFK- und Fake-Offline-Spieler zurück + for (UUID uuid : new HashSet<>(afkPlayers.keySet())) { + Player player = getServer().getPlayer(uuid); + if (player != null && afkPlayers.getOrDefault(uuid, false)) { + setPlayerAfk(player, false); + } + } + for (UUID uuid : new HashSet<>(fakeOfflinePlayers.keySet())) { + Player player = getServer().getPlayer(uuid); + if (player != null && fakeOfflinePlayers.getOrDefault(uuid, false)) { + setPlayerFakeOffline(player, false); + } + } + + saveSigns(); + savePlayers(); + } + + // ------------------------------------------------------------ + // Config / Storage + // ------------------------------------------------------------ + private void loadConfig() { + configFile = new File(getDataFolder(), "config.yml"); + try { + if (!getDataFolder().exists() && !getDataFolder().mkdirs()) { + getLogger().severe("Konnte Plugin-Verzeichnis nicht erstellen!"); + return; + } + if (!configFile.exists()) { + saveResource("config.yml", false); + getLogger().info("config.yml wurde erstellt."); + } + config = YamlConfiguration.loadConfiguration(configFile); + + // Defaults + prefix = config.getString("prefix", "Status"); + onlineColor = config.getString("colors.online", "&a"); + afkColor = config.getString("colors.afk", "&e"); + offlineColor = config.getString("colors.offline", "&c"); + playerColor = config.getString("colors.player", "&9"); + dateFormat = config.getString("date-format", "dd.MM.yyyy HH:mm"); + afkTimeout = config.getLong("afk-timeout-seconds", 300) * 1000L; + afkChangeGamemode = config.getBoolean("afk-change-gamemode", true); + } catch (Exception e) { + getLogger().severe("Fehler beim Laden von config.yml: " + e.getMessage()); + e.printStackTrace(); + config = new YamlConfiguration(); // Fallback + } + } + + private void ensureSignsFile() throws IOException { + if (signsFile == null) { + signsFile = new File(getDataFolder(), "playerstatussign_signs.yml"); + } + if (!signsFile.exists()) { + signsFile.createNewFile(); + getLogger().info("playerstatussign_signs.yml erstellt."); + } + if (signsConfig == null) { + signsConfig = YamlConfiguration.loadConfiguration(signsFile); + } + } + + private void loadSigns() { + try { + ensureSignsFile(); + signLocations.clear(); + + if (!signsConfig.contains("signs")) { + getLogger().info("Keine Schilder gefunden."); + return; + } + + for (String key : signsConfig.getConfigurationSection("signs").getKeys(false)) { + String world = signsConfig.getString("signs." + key + ".world"); + int x = signsConfig.getInt("signs." + key + ".x"); + int y = signsConfig.getInt("signs." + key + ".y"); + int z = signsConfig.getInt("signs." + key + ".z"); + String playerName = signsConfig.getString("signs." + key + ".player"); + + if (world != null && playerName != null) { + Location loc = new Location(getServer().getWorld(world), x, y, z); + if (loc.getWorld() != null) { + signLocations.put(loc, playerName); + } + } + } + getLogger().info("playerstatussign_signs.yml geladen. (" + signLocations.size() + " Schilder)"); + } catch (IOException e) { + getLogger().severe("Fehler beim Laden von playerstatussign_signs.yml: " + e.getMessage()); + e.printStackTrace(); + } + } + + private void saveSigns() { + try { + ensureSignsFile(); + + signsConfig.set("signs", null); // Reset + for (Map.Entry entry : signLocations.entrySet()) { + Location loc = entry.getKey(); + String key = locationKey(loc); + + signsConfig.set("signs." + key + ".world", loc.getWorld().getName()); + signsConfig.set("signs." + key + ".x", loc.getBlockX()); + signsConfig.set("signs." + key + ".y", loc.getBlockY()); + signsConfig.set("signs." + key + ".z", loc.getBlockZ()); + signsConfig.set("signs." + key + ".player", entry.getValue()); + } + signsConfig.save(signsFile); + getLogger().info("playerstatussign_signs.yml erfolgreich gespeichert."); + } catch (IOException e) { + getLogger().severe("Fehler beim Speichern von playerstatussign_signs.yml: " + e.getMessage()); + e.printStackTrace(); + } + } + + private void loadPlayers() { + playersFile = new File(getDataFolder(), "playerstatussign_players.yml"); + try { + if (!playersFile.exists()) { + playersFile.createNewFile(); + getLogger().info("playerstatussign_players.yml erstellt."); + } + playersConfig = YamlConfiguration.loadConfiguration(playersFile); + getLogger().info("playerstatussign_players.yml geladen."); + } catch (IOException e) { + getLogger().severe("Fehler beim Erstellen oder Laden von playerstatussign_players.yml: " + e.getMessage()); + e.printStackTrace(); + } + } + + private void savePlayers() { + if (playersConfig == null || playersFile == null) { + getLogger().severe("Fehler: playersConfig oder playersFile ist null!"); + return; + } + try { + playersConfig.save(playersFile); + getLogger().info("playerstatussign_players.yml erfolgreich gespeichert."); + } catch (IOException e) { + getLogger().severe("Fehler beim Speichern von playerstatussign_players.yml: " + e.getMessage()); + e.printStackTrace(); + } + } + + // ------------------------------------------------------------ + // Utils + // ------------------------------------------------------------ + private String getMessage(String key) { + if (config == null) { + getLogger().warning("Konfigurationsdatei nicht geladen! Schlüssel: " + key); + return ChatColor.RED + "Fehler: Nachricht nicht gefunden (" + key + ")"; + } + String message = config.getString("messages." + key, "Message not found: " + key); + return ChatColor.translateAlternateColorCodes('&', message); + } + + private String locationKey(Location loc) { + return loc.getWorld().getName() + "_" + loc.getBlockX() + "_" + loc.getBlockY() + "_" + loc.getBlockZ(); + } + + // ------------------------------------------------------------ + // Scheduler + // ------------------------------------------------------------ + private void startSignUpdater() { + new BukkitRunnable() { + @Override + public void run() { + // Prüfe Inaktivität für alle Spieler + for (Player player : getServer().getOnlinePlayers()) { + UUID uuid = player.getUniqueId(); + if (!afkPlayers.getOrDefault(uuid, false) && !fakeOfflinePlayers.getOrDefault(uuid, false)) { + Long lastMove = lastPlayerMovement.get(uuid); + if (lastMove != null && System.currentTimeMillis() - lastMove > afkTimeout) { + setPlayerAfk(player, true); + } + } + } + + // Aktualisiere Schilder + List toRemove = new ArrayList<>(); + for (Map.Entry entry : signLocations.entrySet()) { + Location loc = entry.getKey(); + String playerName = entry.getValue(); + try { + if (loc.getBlock().getState() instanceof Sign) { + Sign sign = (Sign) loc.getBlock().getState(); + updateSign(sign, playerName); + } else { + toRemove.add(loc); + } + } catch (Exception e) { + getLogger().warning("Fehler beim Aktualisieren des Schildes bei " + loc + ": " + e.getMessage()); + } + } + if (!toRemove.isEmpty()) { + for (Location loc : toRemove) { + signLocations.remove(loc); + String key = locationKey(loc); + signsConfig.set("signs." + key, null); + } + saveSigns(); + } + } + }.runTaskTimer(this, 0L, 100L); // alle 5 Sekunden + } + + // ------------------------------------------------------------ + // Core + // ------------------------------------------------------------ + private void updateSign(Sign sign, String playerName) { + Player player = getServer().getPlayerExact(playerName); + String line1, line2, line3, line4; + + line2 = ChatColor.translateAlternateColorCodes('&', playerColor + playerName); // Spielername + + if (player != null && player.isOnline() && !fakeOfflinePlayers.getOrDefault(player.getUniqueId(), false)) { + if (isAfk(player)) { + // AFK + line1 = ChatColor.translateAlternateColorCodes('&', "x-" + afkColor + "^&r-x"); + line3 = ChatColor.translateAlternateColorCodes('&', afkColor + "AFK"); + line4 = ChatColor.translateAlternateColorCodes('&', "x-" + afkColor + "v&r-x"); + } else { + // Online + line1 = ChatColor.translateAlternateColorCodes('&', "x-" + onlineColor + "^&r-x"); + line3 = ChatColor.translateAlternateColorCodes('&', onlineColor + "Online"); + line4 = ChatColor.translateAlternateColorCodes('&', "x-" + onlineColor + "v&r-x"); + } + } else { + // Offline (oder Fake-Offline) + line1 = ChatColor.translateAlternateColorCodes('&', "x-" + offlineColor + "^&r-x"); + long lastLogin = playersConfig.getLong("players." + playerName + ".last-login", -1); + String dateStr = lastLogin != -1 ? new SimpleDateFormat(dateFormat).format(new Date(lastLogin)) : "Unbekannt"; + line3 = ChatColor.translateAlternateColorCodes('&', offlineColor + dateStr); + line4 = ChatColor.translateAlternateColorCodes('&', "x-" + offlineColor + "v&r-x"); + } + + try { + sign.setLine(0, line1); + sign.setLine(1, line2); + sign.setLine(2, line3); + sign.setLine(3, line4); + sign.update(); + } catch (Exception e) { + getLogger().warning("Fehler beim Aktualisieren des Schildes für " + playerName + ": " + e.getMessage()); + } + } + + private boolean isAfk(Player player) { + return afkPlayers.getOrDefault(player.getUniqueId(), false); + } + + private void setPlayerAfk(Player player, boolean afk) { + UUID uuid = player.getUniqueId(); + try { + if (afk && !afkPlayers.getOrDefault(uuid, false)) { + afkPlayers.put(uuid, true); + if (afkChangeGamemode) { + originalGameModes.put(uuid, player.getGameMode()); + player.setGameMode(GameMode.ADVENTURE); + } + player.sendMessage(getMessage("afk-enabled")); + updateSignsForPlayer(player.getName()); + } else if (!afk && afkPlayers.getOrDefault(uuid, false)) { + afkPlayers.put(uuid, false); + if (afkChangeGamemode) { + GameMode originalMode = originalGameModes.getOrDefault(uuid, GameMode.SURVIVAL); + player.setGameMode(originalMode); + originalGameModes.remove(uuid); + } + player.sendMessage(getMessage("afk-disabled")); + updateSignsForPlayer(player.getName()); + } + } catch (Exception e) { + getLogger().warning("Fehler beim Setzen des AFK-Status für " + player.getName() + ": " + e.getMessage()); + } + } + + private void setPlayerFakeOffline(Player player, boolean fakeOffline) { + UUID uuid = player.getUniqueId(); + String playerName = player.getName(); + try { + if (fakeOffline && !fakeOfflinePlayers.getOrDefault(uuid, false)) { + // Spieler wird Fake-Offline + fakeOfflinePlayers.put(uuid, true); + + // Timestamp direkt speichern + long now = System.currentTimeMillis(); + playersConfig.set("players." + playerName + ".last-login", now); + savePlayers(); + + player.sendMessage(getMessage("status-offline-enabled")); + updateSignsForPlayer(playerName); + } else if (!fakeOffline && fakeOfflinePlayers.getOrDefault(uuid, false)) { + // Spieler wieder sichtbar + fakeOfflinePlayers.put(uuid, false); + player.sendMessage(getMessage("status-offline-disabled")); + updateSignsForPlayer(playerName); + } + } catch (Exception e) { + getLogger().warning("Fehler beim Setzen des Fake-Offline-Status für " + playerName + ": " + e.getMessage()); + } + } + + private void updateSignsForPlayer(String playerName) { + for (Map.Entry entry : signLocations.entrySet()) { + if (entry.getValue().equalsIgnoreCase(playerName)) { + Location loc = entry.getKey(); + try { + if (loc.getBlock().getState() instanceof Sign) { + Sign sign = (Sign) loc.getBlock().getState(); + updateSign(sign, playerName); + } + } catch (Exception e) { + getLogger().warning("Fehler beim Aktualisieren des Schildes für " + playerName + " bei " + loc + ": " + e.getMessage()); + } + } + } + } + + // ------------------------------------------------------------ + // GUI + // ------------------------------------------------------------ + private void openStatsGUI(Player viewer, String targetPlayerName) { + try { + Player target = getServer().getPlayerExact(targetPlayerName); + Inventory gui = getServer().createInventory(null, 9, ChatColor.translateAlternateColorCodes('&', "&6Statistiken von " + targetPlayerName)); + + long playTimeTicks = 0; + int deaths = 0; + int playerKills = 0; + int blocksMined = 0; + double distanceWalkedCm = 0; + + if (target != null && target.isOnline()) { + playTimeTicks = target.getStatistic(Statistic.PLAY_ONE_MINUTE); + deaths = target.getStatistic(Statistic.DEATHS); + playerKills = target.getStatistic(Statistic.PLAYER_KILLS); + for (Material material : Material.values()) { + if (material.isBlock()) { + try { + blocksMined += target.getStatistic(Statistic.MINE_BLOCK, material); + } catch (IllegalArgumentException ignored) { } + } + } + distanceWalkedCm = target.getStatistic(Statistic.WALK_ONE_CM); + } else { + playTimeTicks = playersConfig.getLong ("players." + targetPlayerName + ".stats.play-time", 0); + deaths = playersConfig.getInt ("players." + targetPlayerName + ".stats.deaths", 0); + playerKills = playersConfig.getInt ("players." + targetPlayerName + ".stats.player-kills", 0); + blocksMined = playersConfig.getInt ("players." + targetPlayerName + ".stats.blocks-mined", 0); + distanceWalkedCm = playersConfig.getDouble("players." + targetPlayerName + ".stats.distance-walked", 0); + } + + double playTimeHours = playTimeTicks / 72000.0; // 20 Ticks * 3600 Sekunden + double distanceWalkedKm = distanceWalkedCm / 100000.0; + + ItemStack playTimeItem = new ItemStack(Material.CLOCK); + ItemMeta playTimeMeta = playTimeItem.getItemMeta(); + if (playTimeMeta != null) { + playTimeMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&eSpielzeit")); + playTimeMeta.setLore(Collections.singletonList(ChatColor.translateAlternateColorCodes('&', "&7" + String.format("%.2f", playTimeHours) + " Stunden"))); + playTimeItem.setItemMeta(playTimeMeta); + } + gui.setItem(0, playTimeItem); + + ItemStack deathsItem = new ItemStack(Material.SKELETON_SKULL); + ItemMeta deathsMeta = deathsItem.getItemMeta(); + if (deathsMeta != null) { + deathsMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&cTode")); + deathsMeta.setLore(Collections.singletonList(ChatColor.translateAlternateColorCodes('&', "&7" + deaths + " Tode"))); + deathsItem.setItemMeta(deathsMeta); + } + gui.setItem(1, deathsItem); + + ItemStack killsItem = new ItemStack(Material.DIAMOND_SWORD); + ItemMeta killsMeta = killsItem.getItemMeta(); + if (killsMeta != null) { + killsMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&aSpieler-Kills")); + killsMeta.setLore(Collections.singletonList(ChatColor.translateAlternateColorCodes('&', "&7" + playerKills + " Kills"))); + killsItem.setItemMeta(killsMeta); + } + gui.setItem(2, killsItem); + + ItemStack blocksMinedItem = new ItemStack(Material.IRON_PICKAXE); + ItemMeta blocksMinedMeta = blocksMinedItem.getItemMeta(); + if (blocksMinedMeta != null) { + blocksMinedMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&bAbgebaute Blöcke")); + blocksMinedMeta.setLore(Collections.singletonList(ChatColor.translateAlternateColorCodes('&', "&7" + blocksMined + " Blöcke"))); + blocksMinedItem.setItemMeta(blocksMinedMeta); + } + gui.setItem(3, blocksMinedItem); + + ItemStack distanceItem = new ItemStack(Material.COMPASS); + ItemMeta distanceMeta = distanceItem.getItemMeta(); + if (distanceMeta != null) { + distanceMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&9Gelaufene Distanz")); + distanceMeta.setLore(Collections.singletonList(ChatColor.translateAlternateColorCodes('&', "&7" + String.format("%.2f", distanceWalkedKm) + " km"))); + distanceItem.setItemMeta(distanceMeta); + } + gui.setItem(4, distanceItem); + + viewer.openInventory(gui); + } catch (Exception e) { + getLogger().warning("Fehler beim Öffnen der Statistik-GUI für " + targetPlayerName + ": " + e.getMessage()); + viewer.sendMessage(getMessage("gui-error")); + } + } + + // ------------------------------------------------------------ + // Commands + // ------------------------------------------------------------ + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(getMessage("player-only")); + return true; + } + + Player player = (Player) sender; + + if (config == null) { + player.sendMessage(ChatColor.RED + "Fehler: Plugin-Konfiguration nicht geladen!"); + getLogger().severe("Konfigurationsdatei nicht verfügbar bei Befehlsausführung!"); + return true; + } + + switch (command.getName().toLowerCase()) { + case "statussign": + if (args.length == 0) { + player.sendMessage(getMessage("help-title")); + player.sendMessage(getMessage("help-reload")); + player.sendMessage(getMessage("help-help")); + return true; + } + + if (args[0].equalsIgnoreCase("reload")) { + if (!player.hasPermission("statussign.admin")) { + player.sendMessage(getMessage("no-permission-admin")); + return true; + } + loadConfig(); + loadSigns(); + loadPlayers(); + player.sendMessage(getMessage("reload-success")); + return true; + } + + if (args[0].equalsIgnoreCase("help")) { + player.sendMessage(getMessage("help-title")); + player.sendMessage(getMessage("help-reload")); + player.sendMessage(getMessage("help-help")); + return true; + } + + player.sendMessage(getMessage("unknown-subcommand")); + return true; + + case "statusafk": + if (!player.hasPermission("statussign.admin")) { + player.sendMessage(getMessage("no-permission-admin")); + return true; + } + if (fakeOfflinePlayers.getOrDefault(player.getUniqueId(), false)) { + player.sendMessage(getMessage("cannot-afk-while-offline")); + return true; + } + setPlayerAfk(player, !isAfk(player)); + return true; + + case "statusoffline": + if (!player.hasPermission("statussign.admin")) { + player.sendMessage(getMessage("no-permission-admin")); + return true; + } + if (isAfk(player)) { + setPlayerAfk(player, false); + } + setPlayerFakeOffline(player, true); + return true; + + case "statusonline": + if (!player.hasPermission("statussign.admin")) { + player.sendMessage(getMessage("no-permission-admin")); + return true; + } + setPlayerFakeOffline(player, false); + return true; + + default: + return false; + } + } + + // ------------------------------------------------------------ + // Events + // ------------------------------------------------------------ + @EventHandler(priority = EventPriority.LOW) + public void onSignChange(SignChangeEvent event) { + if (event.isCancelled()) return; + Player player = event.getPlayer(); + if (!player.hasPermission("statussign.admin")) { + player.sendMessage(getMessage("no-permission-admin")); + event.setCancelled(true); + return; + } + + if (event.getLine(0).equalsIgnoreCase("[" + prefix + "]")) { + String playerName = event.getLine(1); + if (playerName == null || playerName.trim().isEmpty()) { + player.sendMessage(getMessage("invalid-player")); + event.setCancelled(true); + return; + } + + try { + Location loc = event.getBlock().getLocation(); + String key = locationKey(loc); + + signLocations.put(loc, playerName); + + ensureSignsFile(); + signsConfig.set("signs." + key + ".world", loc.getWorld().getName()); + signsConfig.set("signs." + key + ".x", loc.getBlockX()); + signsConfig.set("signs." + key + ".y", loc.getBlockY()); + signsConfig.set("signs." + key + ".z", loc.getBlockZ()); + signsConfig.set("signs." + key + ".player", playerName); + signsConfig.save(signsFile); + + // Sofortige Status-Anzeige + Sign sign = (Sign) event.getBlock().getState(); + updateSign(sign, playerName); + player.sendMessage(getMessage("sign-created").replace("%player%", playerName)); + } catch (Exception e) { + getLogger().warning("Fehler beim Erstellen des Schildes für " + playerName + ": " + e.getMessage()); + player.sendMessage(getMessage("sign-error")); + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.LOW) + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.isCancelled()) return; + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; + if (event.getClickedBlock() == null || !(event.getClickedBlock().getState() instanceof Sign)) return; + + Sign sign = (Sign) event.getClickedBlock().getState(); + Location loc = sign.getLocation(); + if (!signLocations.containsKey(loc)) return; + + Player player = event.getPlayer(); + if (!player.hasPermission("statussign.use")) { + player.sendMessage(getMessage("no-permission-use")); + event.setCancelled(true); + return; + } + + String targetPlayer = signLocations.get(loc); + openStatsGUI(player, targetPlayer); + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.LOW) + public void onBlockBreak(BlockBreakEvent event) { + if (event.isCancelled()) return; + if (!(event.getBlock().getState() instanceof Sign)) return; + + Location loc = event.getBlock().getLocation(); + if (!signLocations.containsKey(loc)) return; + + Player player = event.getPlayer(); + if (!player.hasPermission("statussign.admin")) { + event.setCancelled(true); + player.sendMessage(getMessage("no-permission-break-sign")); + return; + } + if (!player.isSneaking()) { + event.setCancelled(true); + player.sendMessage(getMessage("break-sign-sneak")); + return; + } + + try { + String playerName = signLocations.get(loc); + signLocations.remove(loc); + + String key = locationKey(loc); + ensureSignsFile(); + signsConfig.set("signs." + key, null); + signsConfig.save(signsFile); + + player.sendMessage(getMessage("sign-removed").replace("%player%", playerName)); + } catch (Exception e) { + getLogger().warning("Fehler beim Entfernen des Schildes bei " + loc + ": " + e.getMessage()); + player.sendMessage(getMessage("sign-error")); + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOW) + public void onPlayerMove(PlayerMoveEvent event) { + if (event.isCancelled()) return; + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + lastPlayerMovement.put(uuid, System.currentTimeMillis()); + if (isAfk(player) && !fakeOfflinePlayers.getOrDefault(uuid, false)) { + setPlayerAfk(player, false); + } + } + + @EventHandler(priority = EventPriority.LOW) + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + String playerName = player.getName(); + UUID uuid = player.getUniqueId(); + + try { + // Speichere Statistiken + playersConfig.set("players." + playerName + ".last-login", System.currentTimeMillis()); + playersConfig.set("players." + playerName + ".stats.play-time", player.getStatistic(Statistic.PLAY_ONE_MINUTE)); + playersConfig.set("players." + playerName + ".stats.deaths", player.getStatistic(Statistic.DEATHS)); + playersConfig.set("players." + playerName + ".stats.player-kills", player.getStatistic(Statistic.PLAYER_KILLS)); + + int blocksMined = 0; + for (Material material : Material.values()) { + if (material.isBlock()) { + try { + blocksMined += player.getStatistic(Statistic.MINE_BLOCK, material); + } catch (IllegalArgumentException ignored) { } + } + } + playersConfig.set("players." + playerName + ".stats.blocks-mined", blocksMined); + playersConfig.set("players." + playerName + ".stats.distance-walked", player.getStatistic(Statistic.WALK_ONE_CM)); + savePlayers(); + + afkPlayers.remove(uuid); + fakeOfflinePlayers.remove(uuid); + originalGameModes.remove(uuid); + lastPlayerMovement.remove(uuid); + } catch (Exception e) { + getLogger().warning("Fehler beim Speichern der Daten für " + playerName + ": " + e.getMessage()); + } + } +} \ No newline at end of file