Update from Git Manager GUI
This commit is contained in:
@@ -45,6 +45,7 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
case "export" -> handleExport(player, args);
|
case "export" -> handleExport(player, args);
|
||||||
case "import" -> handleImport(player, args);
|
case "import" -> handleImport(player, args);
|
||||||
case "stats" -> handleStats(player);
|
case "stats" -> handleStats(player);
|
||||||
|
case "top" -> handleTop(player);
|
||||||
case "archive" -> handleArchive(player);
|
case "archive" -> handleArchive(player);
|
||||||
case "comment" -> handleComment(player, args);
|
case "comment" -> handleComment(player, args);
|
||||||
case "blacklist" -> handleBlacklist(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<String[]> 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 ────────────────────────────
|
// ─────────────────────────── /ticket reload ────────────────────────────
|
||||||
|
|
||||||
private void handleReload(Player player) {
|
private void handleReload(Player player) {
|
||||||
@@ -575,6 +615,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
var stats = plugin.getDatabaseManager().getTicketStats();
|
var stats = plugin.getDatabaseManager().getTicketStats();
|
||||||
var staffRatings = plugin.getDatabaseManager().getStaffRatings();
|
var staffRatings = plugin.getDatabaseManager().getStaffRatings();
|
||||||
|
// Persistente Ersteller-Statistik – überlebt Löschen/Archivieren
|
||||||
|
var topCreators = plugin.getDatabaseManager().getTopCreators(5);
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
player.sendMessage(plugin.color("&8&m "));
|
player.sendMessage(plugin.color("&8&m "));
|
||||||
player.sendMessage(plugin.color("&6Ticket Statistik"));
|
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("&8&m "));
|
||||||
player.sendMessage(plugin.color("&6Top Ersteller:"));
|
player.sendMessage(plugin.color("&6Top-5 Ticket-Ersteller &7(historisch, persistent)"));
|
||||||
stats.byPlayer.entrySet().stream()
|
if (topCreators.isEmpty()) {
|
||||||
.sorted((a, b) -> b.getValue() - a.getValue())
|
player.sendMessage(plugin.color("&7Noch keine Daten vorhanden."));
|
||||||
.limit(5)
|
} else {
|
||||||
.forEach(e -> player.sendMessage(plugin.color("&e " + e.getKey() + ": &a" + e.getValue())));
|
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 "));
|
player.sendMessage(plugin.color("&8&m "));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -739,7 +791,7 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
if (!(sender instanceof Player player)) return completions;
|
if (!(sender instanceof Player player)) return completions;
|
||||||
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
List<String> subs = new ArrayList<>(List.of("create", "list", "comment"));
|
List<String> subs = new ArrayList<>(List.of("create", "list", "comment", "top"));
|
||||||
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin"))
|
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin"))
|
||||||
subs.addAll(List.of("claim", "close"));
|
subs.addAll(List.of("claim", "close"));
|
||||||
if (plugin.getConfig().getBoolean("rating-enabled", true)) subs.add("rate");
|
if (plugin.getConfig().getBoolean("rating-enabled", true)) subs.add("rate");
|
||||||
|
|||||||
@@ -265,6 +265,20 @@ public class DatabaseManager {
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) 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()) {
|
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
|
||||||
stmt.execute(ticketsSql);
|
stmt.execute(ticketsSql);
|
||||||
stmt.execute(commentsSql);
|
stmt.execute(commentsSql);
|
||||||
@@ -272,6 +286,7 @@ public class DatabaseManager {
|
|||||||
stmt.execute(notifSql);
|
stmt.execute(notifSql);
|
||||||
stmt.execute(statsSql);
|
stmt.execute(statsSql);
|
||||||
stmt.execute(pendingTeleportSql);
|
stmt.execute(pendingTeleportSql);
|
||||||
|
stmt.execute(creatorStatsSql);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Fehler beim Erstellen der Tabellen: " + e.getMessage(), 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.setString(12, ticket.getServerName()); // BungeeCord
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
ResultSet rs = ps.getGeneratedKeys();
|
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) {
|
} catch (SQLException e) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Fehler beim Erstellen des Tickets: " + e.getMessage(), e);
|
plugin.getLogger().log(Level.SEVERE, "Fehler beim Erstellen des Tickets: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
@@ -375,11 +394,87 @@ public class DatabaseManager {
|
|||||||
ticket.setId(id);
|
ticket.setId(id);
|
||||||
dataConfig.set("lastId", id);
|
dataConfig.set("lastId", id);
|
||||||
dataConfig.set("tickets." + id, ticket);
|
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();
|
saveDataConfig();
|
||||||
return id;
|
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<String[]> getTopCreators(int limit) {
|
||||||
|
List<String[]> 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<Entry> 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 ──────────────────────────────────────────────────────────
|
// ── BUG FIX #1 ──────────────────────────────────────────────────────────
|
||||||
// Vorher: WHERE id = ? AND status = 'OPEN'
|
// Vorher: WHERE id = ? AND status = 'OPEN'
|
||||||
// Problem: Ein FORWARDED-Ticket konnte nicht geclaimed werden – das UPDATE
|
// Problem: Ein FORWARDED-Ticket konnte nicht geclaimed werden – das UPDATE
|
||||||
|
|||||||
Reference in New Issue
Block a user