diff --git a/src/main/java/PlayerStatusSign.java b/src/main/java/PlayerStatusSign.java index 4dfaa2a..5e7f980 100644 --- a/src/main/java/PlayerStatusSign.java +++ b/src/main/java/PlayerStatusSign.java @@ -1,4 +1,5 @@ -import org.bstats.bukkit.Metrics; // bStats import +import org.bstats.bukkit.Metrics; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.GameMode; import org.bukkit.Location; @@ -16,7 +17,10 @@ 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.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.Inventory; @@ -27,8 +31,12 @@ import org.bukkit.scheduler.BukkitRunnable; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.net.URL; import java.text.SimpleDateFormat; import java.util.*; +import java.util.Scanner; +import java.util.function.Consumer; public class PlayerStatusSign extends JavaPlugin implements Listener { @@ -39,11 +47,11 @@ public class PlayerStatusSign extends JavaPlugin implements Listener { 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 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; @@ -61,6 +69,10 @@ public class PlayerStatusSign extends JavaPlugin implements Listener { private static final int BSTATS_PLUGIN_ID = 26877; // <-- bStats Service-ID hier eintragen! private Metrics metrics; + // Neue Variablen zum Speichern des Update-Status + private boolean updateAvailable = false; + private String latestVersion = null; + // ------------------------------------------------------------ // Lifecycle // ------------------------------------------------------------ @@ -84,10 +96,34 @@ public class PlayerStatusSign extends JavaPlugin implements Listener { metrics = new Metrics(this, BSTATS_PLUGIN_ID); getLogger().info("bStats wurde gestartet (Plugin-ID: " + BSTATS_PLUGIN_ID + ")"); + // ---- Spigot Update Checker ---- + int resourceId = 127335; // Ersetze dies mit der ID deiner Plugin-Ressource auf SpigotMC + new UpdateChecker(this, resourceId).getVersion(version -> { + if (this.getDescription().getVersion().equals(version)) { + getLogger().info("Das Plugin ist auf dem neuesten Stand."); + updateAvailable = false; + latestVersion = null; + } else { + getLogger().info("Eine neue Plugin-Version ist verfügbar: " + version); + updateAvailable = true; + latestVersion = version; + + // Sofort Nachricht an alle Operatoren oder “statussign.admin”-Permission + Bukkit.getScheduler().runTask(this, () -> { + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.isOp() || player.hasPermission("statussign.admin")) { + player.sendMessage(ChatColor.GREEN + "Es ist eine neue Version von PlayerStatusSign verfügbar: " + ChatColor.GOLD + version); + player.sendMessage(ChatColor.GREEN + "Lade sie auf SpigotMC herunter!"); + } + } + }); + } + }); + // Konsolenmeldung beim Plugin-Start getLogger().info("[>] ========================================== [<]"); - getLogger().info(" PlayerStatusSign - " + getDescription().getVersion() + " (cfg " + CONFIG_VERSION + ")"); - getLogger().info(" Ersteller: " + PLUGIN_AUTHOR); + 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()); @@ -116,6 +152,47 @@ public class PlayerStatusSign extends JavaPlugin implements Listener { savePlayers(); } + // Spieler-Join Event für Ingame Update-Nachricht + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if (updateAvailable && (player.isOp() || player.hasPermission("statussign.admin"))) { + player.sendMessage(ChatColor.GREEN + "Es ist eine neue Version von PlayerStatusSign verfügbar: " + ChatColor.GOLD + latestVersion); + player.sendMessage(ChatColor.GREEN + "Lade sie auf SpigotMC herunter!"); + } + } + + // ... (restliche Methoden wie loadConfig, EventHandler etc. hier weiterhin einfügen) ... + + // ------------------------------------------------------------ + // UpdateChecker Klasse (innerhalb derselben Datei oder als separate Datei) + // ------------------------------------------------------------ + public static class UpdateChecker { + + private final JavaPlugin plugin; + private final int resourceId; + + public UpdateChecker(JavaPlugin plugin, int resourceId) { + this.plugin = plugin; + this.resourceId = resourceId; + } + + public void getVersion(final Consumer consumer) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try (InputStream is = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + resourceId).openStream(); + Scanner scanner = new Scanner(is)) { + if (scanner.hasNext()) { + consumer.accept(scanner.next()); + } + } catch (IOException e) { + plugin.getLogger().warning("Unable to check for updates: " + e.getMessage()); + } + }); + } + } + + + // ------------------------------------------------------------ // Config / Storage // ------------------------------------------------------------ @@ -420,93 +497,180 @@ public class PlayerStatusSign extends JavaPlugin implements Listener { } // ------------------------------------------------------------ - // 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)); +// GUI +// ------------------------------------------------------------ +private void openStatsGUI(Player viewer, String targetPlayerName) { + try { + Player target = getServer().getPlayerExact(targetPlayerName); + Inventory gui = getServer().createInventory(null, 9, ChatColor.translateAlternateColorCodes('&', "&3Statistiken von " + targetPlayerName)); - long playTimeTicks = 0; - int deaths = 0; - int playerKills = 0; - int blocksMined = 0; - double distanceWalkedCm = 0; + 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) { } - } + 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")); + 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; + double distanceWalkedKm = distanceWalkedCm / 100000.0; + + // Spielzeit + 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); + + // Tode + 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); + + // Kills + 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); + + // Abgebaute Blöcke + 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); + + // Gelaufene Distanz + 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); + + // NEU 5: Geflogene Distanz + double flewDistanceCm = (target != null && target.isOnline()) ? + target.getStatistic(Statistic.FLY_ONE_CM) : + playersConfig.getDouble("players." + targetPlayerName + ".stats.flew-distance", 0); + double flewDistanceKm = flewDistanceCm / 100000.0; + ItemStack flewDistanceItem = new ItemStack(Material.ELYTRA); + ItemMeta flewMeta = flewDistanceItem.getItemMeta(); + if (flewMeta != null) { + flewMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&9Geflogene Distanz")); + flewMeta.setLore(Collections.singletonList(ChatColor.translateAlternateColorCodes('&', String.format("&7%.2f km", flewDistanceKm)))); + flewDistanceItem.setItemMeta(flewMeta); + } + gui.setItem(5, flewDistanceItem); + + // NEU 6: Crafting + int craftedCount = 0; + if (target != null && target.isOnline()) { + for (Material m : Material.values()) { + if (m.isItem()) { + try { craftedCount += target.getStatistic(Statistic.CRAFT_ITEM, m); } catch (Exception ignored) {} + } + } + } else { + craftedCount = playersConfig.getInt("players." + targetPlayerName + ".stats.crafted-items", 0); + } + ItemStack craftedItem = new ItemStack(Material.CRAFTING_TABLE); + ItemMeta craftedMeta = craftedItem.getItemMeta(); + if (craftedMeta != null) { + craftedMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&eItems hergestellt")); + craftedMeta.setLore(Collections.singletonList(ChatColor.translateAlternateColorCodes('&', "&7" + craftedCount))); + craftedItem.setItemMeta(craftedMeta); + } + gui.setItem(6, craftedItem); + + // NEU 7: Blöcke gesetzt + int blocksPlaced = 0; + if (target != null && target.isOnline()) { + for (Material m : Material.values()) { + if (m.isBlock()) { + try { blocksPlaced += target.getStatistic(Statistic.USE_ITEM, m); } catch (Exception ignored) {} + } + } + } else { + blocksPlaced = playersConfig.getInt("players." + targetPlayerName + ".stats.blocks-placed", 0); + } + ItemStack placedItem = new ItemStack(Material.OAK_PLANKS); + ItemMeta placedMeta = placedItem.getItemMeta(); + if (placedMeta != null) { + placedMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&aGesetzte Blöcke")); + placedMeta.setLore(Collections.singletonList(ChatColor.translateAlternateColorCodes('&', "&7" + blocksPlaced))); + placedItem.setItemMeta(placedMeta); + } + gui.setItem(7, placedItem); + + // NEU 8: Zeit seit letztem Tod + long lastDeathMs; + if (target != null && target.isOnline()) { + lastDeathMs = System.currentTimeMillis() - (target.getStatistic(Statistic.TIME_SINCE_DEATH) * 50L); + } else { + long recordedTime = playersConfig.getLong("players." + targetPlayerName + ".stats.last-death-time", System.currentTimeMillis()); + lastDeathMs = System.currentTimeMillis() - recordedTime; + } + // Zeit seit letztem Tod in Stunden + if (target != null && target.isOnline()) { + lastDeathMs = System.currentTimeMillis() - (target.getStatistic(Statistic.TIME_SINCE_DEATH) * 50L); + } else { + long recordedTime = playersConfig.getLong("players." + targetPlayerName + ".stats.last-death-time", System.currentTimeMillis()); + lastDeathMs = System.currentTimeMillis() - recordedTime; + } + double hoursSinceDeath = lastDeathMs / 3600000.0; // Millisekunden → Stunden + ItemStack deathItem = new ItemStack(Material.CLOCK); + ItemMeta deathMeta = deathItem.getItemMeta(); + if (deathMeta != null) { + deathMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', "&6Zeit seit letztem Tod")); + deathMeta.setLore(Collections.singletonList(ChatColor.translateAlternateColorCodes('&', + String.format("&7%.2f Stunden", hoursSinceDeath)))); + deathItem.setItemMeta(deathMeta); + } + gui.setItem(8, deathItem); + + + 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 @@ -662,6 +826,22 @@ public class PlayerStatusSign extends JavaPlugin implements Listener { event.setCancelled(true); } + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + String title = ChatColor.stripColor(event.getView().getTitle()); + if (title != null && title.startsWith("Statistiken von ")) { + event.setCancelled(true); // Verhindert Herausnehmen/Verschieben von Items im Stats-GUI + } + } + + @EventHandler + public void onInventoryDrag(InventoryDragEvent event) { + String title = ChatColor.stripColor(event.getView().getTitle()); + if (title != null && title.startsWith("Statistiken von ")) { + event.setCancelled(true); // Verhindert Drag & Drop im Stats-GUI + } + } + @EventHandler(priority = EventPriority.LOW) public void onBlockBreak(BlockBreakEvent event) { if (event.isCancelled()) return;