From 301c0f1ce9c3dadd83e351dcb6baf0bf20d3cae9 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sat, 21 Feb 2026 18:41:15 +0100 Subject: [PATCH] Update from Git Manager GUI --- .../ticketsystem/commands/TicketCommand.java | 64 ++++++++++-- .../database/DatabaseManager.java | 97 ++++++++++++++++++- 2 files changed, 154 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/ticketsystem/commands/TicketCommand.java b/src/main/java/de/ticketsystem/commands/TicketCommand.java index a2afb88..a83a8e8 100644 --- a/src/main/java/de/ticketsystem/commands/TicketCommand.java +++ b/src/main/java/de/ticketsystem/commands/TicketCommand.java @@ -45,6 +45,7 @@ public class TicketCommand implements CommandExecutor, TabCompleter { case "export" -> handleExport(player, args); case "import" -> handleImport(player, args); case "stats" -> handleStats(player); + case "top" -> handleTop(player); case "archive" -> handleArchive(player); case "comment" -> handleComment(player, args); case "blacklist" -> handleBlacklist(player, args); @@ -543,6 +544,45 @@ public class TicketCommand implements CommandExecutor, TabCompleter { } } + // ─────────────────────────── /ticket top ────────────────────────────── + + /** + * Zeigt das Leaderboard der Top-5 Ticket-Ersteller. + * Basiert auf der ticket_creator_stats-Tabelle, die Werte auch nach + * dem Löschen oder Archivieren von Tickets beibehält. + * Berechtigung: ticket.create (alle Spieler) + */ + private void handleTop(Player player) { + if (!player.hasPermission("ticket.create") && !player.hasPermission("ticket.admin")) { + player.sendMessage(plugin.formatMessage("messages.no-permission")); + return; + } + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + List top = plugin.getDatabaseManager().getTopCreators(5); + Bukkit.getScheduler().runTask(plugin, () -> { + player.sendMessage(plugin.color("&8&m ")); + player.sendMessage(plugin.color("&6&lTop-5 Ticket-Ersteller")); + player.sendMessage(plugin.color("&8&m ")); + if (top.isEmpty()) { + player.sendMessage(plugin.color("&7Noch keine Daten vorhanden.")); + } else { + String[] medals = {"&e🥇", "&7🥈", "&6🥉", "&7#4", "&7#5"}; + for (String[] entry : top) { + int rankIdx = Integer.parseInt(entry[0]) - 1; + String medal = rankIdx < medals.length ? medals[rankIdx] : "&7#" + entry[0]; + String name = entry[1]; + String count = entry[2]; + player.sendMessage(plugin.color( + medal + " &f" + String.format("%-16s", name) + + " &e" + count + " &7Ticket" + (Integer.parseInt(count) == 1 ? "" : "s"))); + } + } + player.sendMessage(plugin.color("&8&m ")); + player.sendMessage(plugin.color("&7(Zähler bleiben auch nach dem Löschen von Tickets erhalten)")); + }); + }); + } + // ─────────────────────────── /ticket reload ──────────────────────────── private void handleReload(Player player) { @@ -575,6 +615,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { var stats = plugin.getDatabaseManager().getTicketStats(); var staffRatings = plugin.getDatabaseManager().getStaffRatings(); + // Persistente Ersteller-Statistik – überlebt Löschen/Archivieren + var topCreators = plugin.getDatabaseManager().getTopCreators(5); Bukkit.getScheduler().runTask(plugin, () -> { player.sendMessage(plugin.color("&8&m ")); player.sendMessage(plugin.color("&6Ticket Statistik")); @@ -623,11 +665,21 @@ public class TicketCommand implements CommandExecutor, TabCompleter { } player.sendMessage(plugin.color("&8&m ")); - player.sendMessage(plugin.color("&6Top Ersteller:")); - stats.byPlayer.entrySet().stream() - .sorted((a, b) -> b.getValue() - a.getValue()) - .limit(5) - .forEach(e -> player.sendMessage(plugin.color("&e " + e.getKey() + ": &a" + e.getValue()))); + player.sendMessage(plugin.color("&6Top-5 Ticket-Ersteller &7(historisch, persistent)")); + if (topCreators.isEmpty()) { + player.sendMessage(plugin.color("&7Noch keine Daten vorhanden.")); + } else { + String[] medals = {"&e🥇", "&7🥈", "&6🥉", "&7#4", "&7#5"}; + for (String[] entry : topCreators) { + int rankIdx = Integer.parseInt(entry[0]) - 1; + String medal = rankIdx < medals.length ? medals[rankIdx] : "&7#" + entry[0]; + String name = String.format("%-16s", entry[1]); + String count = entry[2]; + player.sendMessage(plugin.color( + " " + medal + " &f" + name + " &e" + count + + " &7Ticket" + (Integer.parseInt(count) == 1 ? "" : "s"))); + } + } player.sendMessage(plugin.color("&8&m ")); }); }); @@ -739,7 +791,7 @@ public class TicketCommand implements CommandExecutor, TabCompleter { if (!(sender instanceof Player player)) return completions; if (args.length == 1) { - List subs = new ArrayList<>(List.of("create", "list", "comment")); + List subs = new ArrayList<>(List.of("create", "list", "comment", "top")); if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) subs.addAll(List.of("claim", "close")); if (plugin.getConfig().getBoolean("rating-enabled", true)) subs.add("rate"); diff --git a/src/main/java/de/ticketsystem/database/DatabaseManager.java b/src/main/java/de/ticketsystem/database/DatabaseManager.java index 8ed9ccf..6aac17f 100644 --- a/src/main/java/de/ticketsystem/database/DatabaseManager.java +++ b/src/main/java/de/ticketsystem/database/DatabaseManager.java @@ -265,6 +265,20 @@ public class DatabaseManager { ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """; + // Persistente Ersteller-Statistik – zählt alle jemals erstellten Tickets pro Spieler. + // Überlebt das Löschen und Archivieren von Tickets vollständig. + // Wird bei jedem createTicket() inkrementiert (INSERT … ON DUPLICATE KEY UPDATE). + String creatorStatsSql = """ + CREATE TABLE IF NOT EXISTS ticket_creator_stats ( + creator_uuid VARCHAR(36) NOT NULL PRIMARY KEY, + creator_name VARCHAR(16) NOT NULL, + ticket_count INT NOT NULL DEFAULT 1, + last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_ticket_count (ticket_count) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + """; + try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) { stmt.execute(ticketsSql); stmt.execute(commentsSql); @@ -272,6 +286,7 @@ public class DatabaseManager { stmt.execute(notifSql); stmt.execute(statsSql); stmt.execute(pendingTeleportSql); + stmt.execute(creatorStatsSql); } catch (SQLException e) { plugin.getLogger().log(Level.SEVERE, "Fehler beim Erstellen der Tabellen: " + e.getMessage(), e); } @@ -365,7 +380,11 @@ public class DatabaseManager { ps.setString(12, ticket.getServerName()); // BungeeCord ps.executeUpdate(); ResultSet rs = ps.getGeneratedKeys(); - if (rs.next()) return rs.getInt(1); + if (rs.next()) { + int newId = rs.getInt(1); + incrementCreatorStats(ticket.getCreatorUUID(), ticket.getCreatorName()); + return newId; + } } catch (SQLException e) { plugin.getLogger().log(Level.SEVERE, "Fehler beim Erstellen des Tickets: " + e.getMessage(), e); } @@ -375,11 +394,87 @@ public class DatabaseManager { ticket.setId(id); dataConfig.set("lastId", id); dataConfig.set("tickets." + id, ticket); + // Datei-Modus: Zähler in dataConfig pflegen + String statsKey = "creator-stats." + ticket.getCreatorUUID().toString(); + int current = dataConfig.getInt(statsKey + ".count", 0); + dataConfig.set(statsKey + ".count", current + 1); + dataConfig.set(statsKey + ".name", ticket.getCreatorName()); saveDataConfig(); return id; } } + // ─────────────────────────── Creator-Leaderboard ──────────────────────── + + /** + * Erhöht den Ticket-Zähler eines Erstellers um 1 (MySQL-Modus). + * Wird nach jedem erfolgreichen createTicket() aufgerufen. + * Die Tabelle ist unabhängig von tickets/archive – Zahlen gehen nie verloren. + */ + private void incrementCreatorStats(UUID creatorUUID, String creatorName) { + if (!useMySQL) return; + String sql = """ + INSERT INTO ticket_creator_stats (creator_uuid, creator_name, ticket_count) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE + ticket_count = ticket_count + 1, + creator_name = VALUES(creator_name) + """; + try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, creatorUUID.toString()); + ps.setString(2, creatorName); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().log(Level.SEVERE, "Fehler beim Aktualisieren der Creator-Stats: " + e.getMessage(), e); + } + } + + /** + * Gibt die Top-{limit} Ticket-Ersteller zurück. + * Jeder Eintrag: [rank, playerName, ticketCount] (alle als String) + * Funktioniert in MySQL- und Datei-Modus. + */ + public List getTopCreators(int limit) { + List result = new ArrayList<>(); + if (useMySQL) { + String sql = """ + SELECT creator_name, ticket_count + FROM ticket_creator_stats + ORDER BY ticket_count DESC + LIMIT ? + """; + try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, limit); + ResultSet rs = ps.executeQuery(); + int rank = 1; + while (rs.next()) { + result.add(new String[]{ + String.valueOf(rank++), + rs.getString("creator_name"), + String.valueOf(rs.getInt("ticket_count")) + }); + } + } catch (SQLException e) { + plugin.getLogger().log(Level.SEVERE, "Fehler beim Abrufen der Top-Creator: " + e.getMessage(), e); + } + } else if (dataConfig != null && dataConfig.contains("creator-stats")) { + // Datei-Modus: alle Einträge einlesen, nach count absteigend sortieren, begrenzen + record Entry(String name, int count) {} + List entries = new ArrayList<>(); + for (String uuid : dataConfig.getConfigurationSection("creator-stats").getKeys(false)) { + String name = dataConfig.getString("creator-stats." + uuid + ".name", uuid); + int count = dataConfig.getInt("creator-stats." + uuid + ".count", 0); + entries.add(new Entry(name, count)); + } + entries.sort((a, b) -> Integer.compare(b.count(), a.count())); + int rank = 1; + for (Entry e : entries.subList(0, Math.min(limit, entries.size()))) { + result.add(new String[]{String.valueOf(rank++), e.name(), String.valueOf(e.count())}); + } + } + return result; + } + // ── BUG FIX #1 ────────────────────────────────────────────────────────── // Vorher: WHERE id = ? AND status = 'OPEN' // Problem: Ein FORWARDED-Ticket konnte nicht geclaimed werden – das UPDATE