diff --git a/src/main/java/PlayerStatusSign.java b/src/main/java/PlayerStatusSign.java index 69b1a14..4dfaa2a 100644 --- a/src/main/java/PlayerStatusSign.java +++ b/src/main/java/PlayerStatusSign.java @@ -1,737 +1,746 @@ -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()); - } - } +import org.bstats.bukkit.Metrics; // bStats import +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"; + + // bStats Metrics-Objekt + private static final int BSTATS_PLUGIN_ID = 26877; // <-- bStats Service-ID hier eintragen! + private Metrics metrics; + + // ------------------------------------------------------------ + // 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(); + + // ---- bStats starten ---- + metrics = new Metrics(this, BSTATS_PLUGIN_ID); + getLogger().info("bStats wurde gestartet (Plugin-ID: " + BSTATS_PLUGIN_ID + ")"); + + // 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