Update from Git Manager GUI

This commit is contained in:
2026-02-21 16:00:03 +01:00
parent 834bd0e5e4
commit 7ede377c07
11 changed files with 1495 additions and 332 deletions

View File

@@ -161,6 +161,7 @@ public class DatabaseManager {
private void createTables() {
// Haupt-Tickets-Tabelle
// BungeeCord: server_name speichert auf welchem Server das Ticket erstellt wurde
String ticketsSql = """
CREATE TABLE IF NOT EXISTS tickets (
id INT AUTO_INCREMENT PRIMARY KEY,
@@ -186,7 +187,9 @@ public class DatabaseManager {
category VARCHAR(16) NOT NULL DEFAULT 'GENERAL',
priority VARCHAR(10) NOT NULL DEFAULT 'NORMAL',
player_rating VARCHAR(16) NULL,
claimer_notified BOOLEAN DEFAULT FALSE
claimer_notified BOOLEAN DEFAULT FALSE,
close_notified BOOLEAN DEFAULT FALSE,
server_name VARCHAR(64) NOT NULL DEFAULT 'unknown'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
@@ -225,11 +228,50 @@ public class DatabaseManager {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
// Persistente Statistik-Tabelle überlebt das Löschen/Archivieren von Tickets.
// Wird beim Schließen eines Tickets befüllt und beim Bewerten aktualisiert.
// So gehen keine Zahlen verloren wenn das Archiv geleert wird.
String statsSql = """
CREATE TABLE IF NOT EXISTS ticket_stats (
id INT AUTO_INCREMENT PRIMARY KEY,
ticket_id INT NOT NULL,
claimer_uuid VARCHAR(36) NULL,
claimer_name VARCHAR(16) NULL,
creator_uuid VARCHAR(36) NOT NULL,
creator_name VARCHAR(16) NOT NULL,
category VARCHAR(16) NOT NULL DEFAULT 'general',
priority VARCHAR(10) NOT NULL DEFAULT 'NORMAL',
server_name VARCHAR(64) NOT NULL DEFAULT 'unknown',
player_rating VARCHAR(16) NULL,
closed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_claimer_uuid (claimer_uuid),
INDEX idx_closed_at (closed_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
// Ausstehende BungeeCord-Teleport-Aufträge.
// Wird gesetzt wenn ein Admin via GUI/Command auf einen anderen Server teleportiert.
// PlayerJoinListener liest den Eintrag beim Ankommen, teleportiert, löscht ihn dann.
String pendingTeleportSql = """
CREATE TABLE IF NOT EXISTS ticket_pending_teleport (
player_uuid VARCHAR(36) NOT NULL PRIMARY KEY,
world VARCHAR(64) NOT NULL,
x DOUBLE NOT NULL,
y DOUBLE NOT NULL,
z DOUBLE NOT NULL,
yaw FLOAT NOT NULL DEFAULT 0,
pitch FLOAT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
stmt.execute(ticketsSql);
stmt.execute(commentsSql);
stmt.execute(blacklistSql);
stmt.execute(notifSql);
stmt.execute(statsSql);
stmt.execute(pendingTeleportSql);
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Erstellen der Tabellen: " + e.getMessage(), e);
}
@@ -237,6 +279,7 @@ public class DatabaseManager {
/**
* Ergänzt fehlende Spalten in bestehenden Datenbanken automatisch.
* Wichtig für Upgrades von älteren Versionen.
*/
private void ensureColumns() {
ensureColumn("close_comment", "ALTER TABLE tickets ADD COLUMN close_comment VARCHAR(500) NULL");
@@ -245,6 +288,13 @@ public class DatabaseManager {
ensureColumn("priority", "ALTER TABLE tickets ADD COLUMN priority VARCHAR(10) NOT NULL DEFAULT 'NORMAL'");
ensureColumn("player_rating", "ALTER TABLE tickets ADD COLUMN player_rating VARCHAR(16) NULL");
ensureColumn("claimer_notified", "ALTER TABLE tickets ADD COLUMN claimer_notified BOOLEAN DEFAULT FALSE");
// Bug-Fix: close_notified verhindert Duplikat-Discord-Nachrichten und doppelte Offline-Benachrichtigungen bei Server-Wechsel
ensureColumn("close_notified", "ALTER TABLE tickets ADD COLUMN close_notified BOOLEAN DEFAULT FALSE");
// BungeeCord: Server-Name-Spalte für bestehende Datenbanken nachrüsten
ensureColumn("server_name", "ALTER TABLE tickets ADD COLUMN server_name VARCHAR(64) NOT NULL DEFAULT 'unknown'");
// ticket_stats: Spalte player_rating nachrüsten falls Tabelle vor diesem Feature existiert
ensureStatsColumn("player_rating", "ALTER TABLE ticket_stats ADD COLUMN player_rating VARCHAR(16) NULL");
}
private void ensureColumn(String columnName, String alterSql) {
@@ -268,13 +318,36 @@ public class DatabaseManager {
}
}
private void ensureStatsColumn(String columnName, String alterSql) {
String checkSql = """
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'ticket_stats'
AND COLUMN_NAME = ?
""";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(checkSql)) {
ps.setString(1, columnName);
ResultSet rs = ps.executeQuery();
if (rs.next() && rs.getInt(1) == 0) {
try (Statement stmt = conn.createStatement()) {
stmt.execute(alterSql);
plugin.getLogger().info("[TicketSystem] Stats-Spalte '" + columnName + "' wurde hinzugefügt.");
}
}
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler bei ensureStatsColumn(" + columnName + "): " + e.getMessage(), e);
}
}
// ─────────────────────────── CRUD Tickets ──────────────────────────────
public int createTicket(Ticket ticket) {
if (useMySQL) {
// BungeeCord: server_name wird ebenfalls gespeichert
String sql = """
INSERT INTO tickets (creator_uuid, creator_name, message, world, x, y, z, yaw, pitch, category, priority)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO tickets (creator_uuid, creator_name, message, world, x, y, z, yaw, pitch,
category, priority, server_name)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
@@ -289,6 +362,7 @@ public class DatabaseManager {
ps.setFloat(9, ticket.getPitch());
ps.setString(10, ticket.getCategoryKey());
ps.setString(11, ticket.getPriority().name());
ps.setString(12, ticket.getServerName()); // BungeeCord
ps.executeUpdate();
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) return rs.getInt(1);
@@ -306,13 +380,21 @@ public class DatabaseManager {
}
}
// ── BUG FIX #1 ──────────────────────────────────────────────────────────
// Vorher: WHERE id = ? AND status = 'OPEN'
// Problem: Ein FORWARDED-Ticket konnte nicht geclaimed werden das UPDATE
// schlug lautlos fehl, claimer_uuid/claimer_name wurden nie geschrieben.
// Fix: WHERE id = ? AND status != 'CLOSED'
// Damit können sowohl OPEN als auch FORWARDED Tickets korrekt
// angenommen werden und claimer_uuid/claimer_name werden immer gesetzt.
// ────────────────────────────────────────────────────────────────────────
public boolean claimTicket(int ticketId, UUID claimerUUID, String claimerName) {
if (useMySQL) {
String sql = """
UPDATE tickets
SET status = 'CLAIMED', claimer_uuid = ?, claimer_name = ?,
claimed_at = NOW(), player_deleted = FALSE
WHERE id = ? AND status = 'OPEN'
WHERE id = ? AND status != 'CLOSED'
""";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, claimerUUID.toString());
@@ -325,7 +407,7 @@ public class DatabaseManager {
return false;
} else {
Ticket t = getTicketById(ticketId);
if (t == null || t.getStatus() != TicketStatus.OPEN) return false;
if (t == null || t.getStatus() == TicketStatus.CLOSED) return false;
t.setStatus(TicketStatus.CLAIMED);
t.setClaimerUUID(claimerUUID);
t.setClaimerName(claimerName);
@@ -452,11 +534,8 @@ public class DatabaseManager {
}
}
// ─────────────────────────── [NEW] Claim-Benachrichtigung markieren ────
// ─────────────────────────── Claim-Benachrichtigung markieren ──────────
/**
* Setzt claimer_notified = TRUE für ein Ticket (persistiert in DB/Datei).
*/
public void markClaimerNotified(int ticketId) {
if (useMySQL) {
String sql = "UPDATE tickets SET claimer_notified = TRUE WHERE id = ?";
@@ -476,21 +555,42 @@ public class DatabaseManager {
}
}
// ─────────────────────────── [NEW] Bewertung ───────────────────────────
// ─────────────────────────── Schließ-Benachrichtigung markieren ────────
/**
* Speichert die Bewertung eines Spielers für sein geschlossenes Ticket.
* @param ticketId ID des Tickets
* @param rating "THUMBS_UP" oder "THUMBS_DOWN"
* @return true bei Erfolg
* Setzt close_notified = TRUE für ein Ticket (persistiert in DB/Datei).
* Verhindert Duplikat-Benachrichtigungen und doppelte Discord-Nachrichten
* bei Server-Wechseln in BungeeCord-Netzwerken.
*/
public void markCloseNotified(int ticketId) {
if (useMySQL) {
String sql = "UPDATE tickets SET close_notified = TRUE WHERE id = ?";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, ticketId);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler bei markCloseNotified: " + e.getMessage(), e);
}
} else {
Ticket t = getTicketById(ticketId);
if (t != null) {
t.setCloseNotified(true);
dataConfig.set("tickets." + ticketId, t);
saveDataConfig();
}
}
}
public boolean rateTicket(int ticketId, String rating) {
if (useMySQL) {
String sql = "UPDATE tickets SET player_rating = ? WHERE id = ? AND status = 'CLOSED' AND player_rating IS NULL";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, rating);
ps.setInt(2, ticketId);
return ps.executeUpdate() > 0;
boolean updated = ps.executeUpdate() > 0;
// Bewertung auch in die persistente Stats-Tabelle übertragen
if (updated) updateStatsRating(ticketId, rating);
return updated;
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler bei rateTicket: " + e.getMessage(), e);
}
@@ -505,11 +605,191 @@ public class DatabaseManager {
}
}
// ─────────────────────────── [NEW] Kommentare ──────────────────────────
/**
* Schreibt einen Eintrag in ticket_stats wenn ein Ticket geschlossen wird.
* Diese Tabelle bleibt dauerhaft erhalten auch wenn das Ticket später
* gelöscht oder archiviert wird. So gehen Statistiken nie verloren.
*
* @param ticket Das gerade geschlossene Ticket-Objekt
* @param closerName Name des Admins/Supporters der das Ticket geschlossen hat
* (kann vom claimer_name abweichen wenn ein Admin fremde Tickets schließt)
*/
public void recordClosedTicket(Ticket ticket, String closerName) {
if (!useMySQL) return;
// closer_name bevorzugen falls null, auf claimer_name zurückfallen
String effectiveCloser = (closerName != null && !closerName.isEmpty())
? closerName : ticket.getClaimerName();
String effectiveCloserUuid = null;
// UUID nur setzen wenn closer == claimer (sonst haben wir die UUID des Admins nicht direkt)
if (effectiveCloser != null && effectiveCloser.equals(ticket.getClaimerName())
&& ticket.getClaimerUUID() != null) {
effectiveCloserUuid = ticket.getClaimerUUID().toString();
}
String sql = """
INSERT INTO ticket_stats
(ticket_id, claimer_uuid, claimer_name, creator_uuid, creator_name,
category, priority, server_name, player_rating, closed_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE closed_at = closed_at
""";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, ticket.getId());
ps.setString(2, effectiveCloserUuid);
ps.setString(3, effectiveCloser);
ps.setString(4, ticket.getCreatorUUID().toString());
ps.setString(5, ticket.getCreatorName());
ps.setString(6, ticket.getCategoryKey());
ps.setString(7, ticket.getPriority().name());
ps.setString(8, ticket.getServerName());
ps.setString(9, ticket.getPlayerRating());
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler bei recordClosedTicket: " + e.getMessage(), e);
}
}
/**
* Speichert einen neuen Kommentar/Reply auf ein Ticket.
* Aktualisiert die Bewertung in ticket_stats wenn ein Spieler sein Ticket bewertet.
* Wird von rateTicket() intern aufgerufen.
*/
private void updateStatsRating(int ticketId, String rating) {
String sql = "UPDATE ticket_stats SET player_rating = ? WHERE ticket_id = ?";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, rating);
ps.setInt(2, ticketId);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler bei updateStatsRating: " + e.getMessage(), e);
}
}
/**
* Gibt eine Liste aller Support-Mitarbeiter mit ihren Bewertungsstatistiken zurück.
* Liest aus ticket_stats unabhängig davon ob die Tickets noch in der DB existieren.
*
* Rückgabe: Liste von String-Arrays mit
* [0] claimer_name, [1] thumbsUp, [2] thumbsDown, [3] total, [4] prozent
*/
public List<String[]> getStaffRatings() {
List<String[]> result = new ArrayList<>();
if (!useMySQL) return result;
String sql = """
SELECT
claimer_name,
SUM(player_rating = 'THUMBS_UP') AS thumbs_up,
SUM(player_rating = 'THUMBS_DOWN') AS thumbs_down,
COUNT(*) AS total_closed
FROM ticket_stats
WHERE claimer_name IS NOT NULL
GROUP BY claimer_uuid, claimer_name
ORDER BY total_closed DESC
""";
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
int up = rs.getInt("thumbs_up");
int down = rs.getInt("thumbs_down");
int total = rs.getInt("total_closed");
int rated = up + down;
String percent = rated > 0 ? Math.round(up * 100.0 / rated) + "%" : "";
result.add(new String[]{
rs.getString("claimer_name"),
String.valueOf(up),
String.valueOf(down),
String.valueOf(total),
percent
});
}
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler bei getStaffRatings: " + e.getMessage(), e);
}
return result;
}
// ─────────────────────────── BungeeCord Pending-Teleport ───────────────
/**
* Speichert einen ausstehenden Teleport-Auftrag für einen Admin/Supporter.
* Wird aufgerufen bevor der Spieler via BungeeCord auf den Ziel-Server
* geschickt wird. Der PlayerJoinListener liest den Eintrag beim Ankommen.
*/
public void setPendingTeleport(UUID playerUUID, String world,
double x, double y, double z,
float yaw, float pitch) {
if (!useMySQL) return;
String sql = """
INSERT INTO ticket_pending_teleport
(player_uuid, world, x, y, z, yaw, pitch)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
world = VALUES(world), x = VALUES(x), y = VALUES(y),
z = VALUES(z), yaw = VALUES(yaw), pitch = VALUES(pitch),
created_at = CURRENT_TIMESTAMP
""";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, playerUUID.toString());
ps.setString(2, world);
ps.setDouble(3, x);
ps.setDouble(4, y);
ps.setDouble(5, z);
ps.setFloat(6, yaw);
ps.setFloat(7, pitch);
ps.executeUpdate();
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler bei setPendingTeleport: " + e.getMessage(), e);
}
}
/**
* Liest und löscht einen ausstehenden Teleport-Auftrag in einem Schritt.
* Gibt null zurück wenn kein Auftrag vorhanden ist.
*
* Rückgabe: double[] { x, y, z, yaw, pitch } + world als Index 0 im String-Array
* Vereinfacht: gibt ein Object[] zurück: [String world, double x, y, z, float yaw, pitch]
*/
public PendingTeleport consumePendingTeleport(UUID playerUUID) {
if (!useMySQL) return null;
String selectSql = "SELECT * FROM ticket_pending_teleport WHERE player_uuid = ?";
String deleteSql = "DELETE FROM ticket_pending_teleport WHERE player_uuid = ?";
try (Connection conn = getConnection();
PreparedStatement sel = conn.prepareStatement(selectSql)) {
sel.setString(1, playerUUID.toString());
ResultSet rs = sel.executeQuery();
if (!rs.next()) return null;
PendingTeleport pt = new PendingTeleport(
rs.getString("world"),
rs.getDouble("x"),
rs.getDouble("y"),
rs.getDouble("z"),
rs.getFloat("yaw"),
rs.getFloat("pitch")
);
// Sofort löschen damit kein zweites Mal teleportiert wird
try (PreparedStatement del = conn.prepareStatement(deleteSql)) {
del.setString(1, playerUUID.toString());
del.executeUpdate();
}
return pt;
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler bei consumePendingTeleport: " + e.getMessage(), e);
return null;
}
}
/** Einfaches Daten-Objekt für einen ausstehenden Teleport-Auftrag. */
public record PendingTeleport(String world, double x, double y, double z, float yaw, float pitch) {}
public boolean addComment(TicketComment comment) {
if (useMySQL) {
String sql = """
@@ -527,7 +807,6 @@ public class DatabaseManager {
}
return false;
} else {
// YAML: comments.<ticketId>.<index>
int index = dataConfig.getInt("comments." + comment.getTicketId() + ".count", 0);
String base = "comments." + comment.getTicketId() + "." + index + ".";
dataConfig.set(base + "authorUUID", comment.getAuthorUUID().toString());
@@ -540,9 +819,6 @@ public class DatabaseManager {
}
}
/**
* Lädt alle Kommentare für ein Ticket, sortiert nach Datum.
*/
public List<TicketComment> getComments(int ticketId) {
List<TicketComment> list = new ArrayList<>();
if (useMySQL) {
@@ -582,12 +858,8 @@ public class DatabaseManager {
return list;
}
// ─────────────────────────── Pending Notifications ────────────────────
// ─────────────────────────── Pending Notifications ────────────────────
/**
* Speichert eine Benachrichtigung für einen offline Spieler.
* Wird beim nächsten Login angezeigt.
*/
public void addPendingNotification(UUID playerUUID, String rawMessage) {
if (useMySQL) {
String sql = "INSERT INTO ticket_pending_notifications (player_uuid, message) VALUES (?, ?)";
@@ -607,9 +879,6 @@ public class DatabaseManager {
}
}
/**
* Lädt alle ausstehenden Benachrichtigungen für einen Spieler.
*/
public List<String> getPendingNotifications(UUID playerUUID) {
List<String> messages = new ArrayList<>();
if (useMySQL) {
@@ -627,9 +896,6 @@ public class DatabaseManager {
return messages;
}
/**
* Löscht alle ausstehenden Benachrichtigungen eines Spielers nach dem Anzeigen.
*/
public void clearPendingNotifications(UUID playerUUID) {
if (useMySQL) {
String sql = "DELETE FROM ticket_pending_notifications WHERE player_uuid = ?";
@@ -645,7 +911,7 @@ public class DatabaseManager {
}
}
// ─────────────────────────── [NEW] Blacklist ───────────────────────────
// ─────────────────────────── Blacklist ─────────────────────────────────
public boolean isBlacklisted(UUID uuid) {
if (useMySQL) {
@@ -705,7 +971,6 @@ public class DatabaseManager {
}
}
/** Gibt alle gesperrten Spieler als Liste von String-Arrays {uuid, name, reason, bannedBy} zurück. */
public List<String[]> getBlacklist() {
List<String[]> list = new ArrayList<>();
if (useMySQL) {
@@ -890,28 +1155,69 @@ public class DatabaseManager {
// ─────────────────────────── Statistiken ───────────────────────────────
public TicketStats getTicketStats() {
// Aktuelle Live-Daten aus der tickets-Tabelle
List<Ticket> all = getAllTickets();
int open = 0, claimed = 0, forwarded = 0, closed = 0, thumbsUp = 0, thumbsDown = 0;
int open = 0, claimed = 0, forwarded = 0, closedLive = 0;
java.util.Map<String, Integer> byPlayer = new java.util.HashMap<>();
java.util.Map<String, Integer> byServer = new java.util.HashMap<>();
for (Ticket t : all) {
switch (t.getStatus()) {
case OPEN -> open++;
case CLAIMED -> claimed++;
case FORWARDED -> forwarded++;
case CLOSED -> closed++;
case CLOSED -> closedLive++;
}
if ("THUMBS_UP".equals(t.getPlayerRating())) thumbsUp++;
if ("THUMBS_DOWN".equals(t.getPlayerRating())) thumbsDown++;
byPlayer.merge(t.getCreatorName(), 1, Integer::sum);
byServer.merge(t.getServerName(), 1, Integer::sum);
}
return new TicketStats(all.size(), open, closed, forwarded, thumbsUp, thumbsDown, byPlayer);
// Historische Bewertungen aus der persistenten Stats-Tabelle lesen
// (enthält auch Daten von bereits gelöschten/archivierten Tickets)
int thumbsUp = 0, thumbsDown = 0, totalClosedEver = closedLive;
if (useMySQL) {
String sql = """
SELECT
COUNT(*) AS total,
SUM(player_rating = 'THUMBS_UP') AS up,
SUM(player_rating = 'THUMBS_DOWN') AS down
FROM ticket_stats
""";
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
totalClosedEver = rs.getInt("total");
thumbsUp = rs.getInt("up");
thumbsDown = rs.getInt("down");
}
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Laden der persistenten Stats: " + e.getMessage(), e);
// Fallback: Bewertungen aus Live-Daten lesen
for (Ticket t : all) {
if ("THUMBS_UP".equals(t.getPlayerRating())) thumbsUp++;
if ("THUMBS_DOWN".equals(t.getPlayerRating())) thumbsDown++;
}
}
} else {
// Datei-Modus: nur Live-Daten verfügbar
for (Ticket t : all) {
if ("THUMBS_UP".equals(t.getPlayerRating())) thumbsUp++;
if ("THUMBS_DOWN".equals(t.getPlayerRating())) thumbsDown++;
}
}
return new TicketStats(all.size(), open, totalClosedEver, forwarded, thumbsUp, thumbsDown, byPlayer, byServer);
}
public static class TicketStats {
public final int total, open, closed, forwarded, thumbsUp, thumbsDown;
public final java.util.Map<String, Integer> byPlayer;
/** BungeeCord: Anzahl Tickets pro Server */
public final java.util.Map<String, Integer> byServer;
public TicketStats(int total, int open, int closed, int forwarded,
int thumbsUp, int thumbsDown, java.util.Map<String, Integer> byPlayer) {
int thumbsUp, int thumbsDown,
java.util.Map<String, Integer> byPlayer,
java.util.Map<String, Integer> byServer) {
this.total = total;
this.open = open;
this.closed = closed;
@@ -919,6 +1225,7 @@ public class DatabaseManager {
this.thumbsUp = thumbsUp;
this.thumbsDown = thumbsDown;
this.byPlayer = byPlayer;
this.byServer = byServer;
}
}
@@ -998,6 +1305,10 @@ public class DatabaseManager {
try { t.setPriority(TicketPriority.fromString(rs.getString("priority"))); } catch (SQLException ignored) {}
try { t.setPlayerRating(rs.getString("player_rating")); } catch (SQLException ignored) {}
try { t.setClaimerNotified(rs.getBoolean("claimer_notified")); } catch (SQLException ignored) {}
// Bug-Fix: close_notified für duplikat-freie Schließ-Benachrichtigungen
try { t.setCloseNotified(rs.getBoolean("close_notified")); } catch (SQLException ignored) {}
// BungeeCord: Server-Name einlesen
try { t.setServerName(rs.getString("server_name")); } catch (SQLException ignored) {}
return t;
}
@@ -1032,6 +1343,9 @@ public class DatabaseManager {
obj.put("priority", t.getPriority().name());
if (t.getPlayerRating() != null) obj.put("playerRating", t.getPlayerRating());
obj.put("claimerNotified", t.isClaimerNotified());
obj.put("closeNotified", t.isCloseNotified());
// BungeeCord: Server-Name im JSON-Export
obj.put("serverName", t.getServerName());
return obj;
}
@@ -1060,6 +1374,9 @@ public class DatabaseManager {
if (obj.containsKey("priority")) t.setPriority(TicketPriority.fromString((String) obj.get("priority")));
if (obj.containsKey("playerRating")) t.setPlayerRating((String) obj.get("playerRating"));
if (obj.containsKey("claimerNotified"))t.setClaimerNotified((Boolean) obj.get("claimerNotified"));
if (obj.containsKey("closeNotified")) t.setCloseNotified((Boolean) obj.get("closeNotified"));
// BungeeCord: Server-Name aus JSON
if (obj.containsKey("serverName")) t.setServerName((String) obj.get("serverName"));
return t;
} catch (Exception e) {
if (plugin != null) plugin.getLogger().severe("Fehler beim Parsen eines Tickets: " + e.getMessage());