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

@@ -100,13 +100,11 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true);
if (args.length >= 3) {
// args[1]: erst als Kategorie prüfen, dann als Priorität
if (categoriesOn) {
ConfigCategory parsedCat = cm.resolve(args[1]);
if (parsedCat != null) {
category = parsedCat;
messageStartIndex = 2;
// args[2]: Priorität prüfen (nur wenn danach noch Text kommt)
if (prioritiesOn && args.length >= 4) {
TicketPriority parsedPrio = parsePriority(args[2]);
if (parsedPrio != null) {
@@ -115,7 +113,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
}
}
} else {
// Keine Kategorie erkannt → args[1] als Priorität prüfen
if (prioritiesOn) {
TicketPriority parsedPrio = parsePriority(args[1]);
if (parsedPrio != null) {
@@ -125,16 +122,12 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
}
}
} else if (prioritiesOn) {
// Kategorien aus → args[1] direkt als Priorität prüfen
TicketPriority parsedPrio = parsePriority(args[1]);
if (parsedPrio != null) {
priority = parsedPrio;
messageStartIndex = 2;
}
}
} else if (args.length == 2) {
// Nur ein Argument: könnte Kategorie oder Priorität sein, aber kein Text danach
// → einfach als Beschreibung behandeln, nichts parsen
}
String message = String.join(" ", Arrays.copyOfRange(args, messageStartIndex, args.length));
@@ -153,6 +146,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
Ticket ticket = new Ticket(player.getUniqueId(), player.getName(), message, player.getLocation());
ticket.setCategoryKey(finalCategory.getKey());
ticket.setPriority(finalPriority);
// BungeeCord: Server-Name des erstellenden Servers speichern
ticket.setServerName(plugin.getServerName());
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
int id = plugin.getDatabaseManager().createTicket(ticket);
@@ -200,11 +195,31 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
if (!success) { player.sendMessage(plugin.formatMessage("messages.already-claimed")); return; }
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
if (ticket == null) return;
player.sendMessage(plugin.formatMessage("messages.ticket-claimed")
.replace("{id}", String.valueOf(ticketId))
.replace("{player}", ticket.getCreatorName()));
plugin.getTicketManager().notifyCreatorClaimed(ticket);
if (ticket.getLocation() != null) player.teleport(ticket.getLocation());
// ── BUG FIX #1: Teleportation bei aktivem BungeeCord komplett sperren ──
// Wenn BungeeCord aktiv ist, kann das Ticket von einem anderen Server stammen.
// getLocation() würde null liefern (World existiert lokal nicht) oder den
// Supporter auf dem falschen Server teleportieren.
// Lösung: Bei aktivem BungeeCord generell keinen Teleport durchführen.
if (plugin.isBungeeCordEnabled()) {
// Hinweis: Server anzeigen wenn bekannt, damit Supporter weiß wo das Ticket ist
String serverHint = !"unknown".equals(ticket.getServerName())
? " &7(Server: &b" + ticket.getServerName() + "&7)"
: "";
player.sendMessage(plugin.color("&7Teleportation deaktiviert BungeeCord-Netzwerk aktiv." + serverHint));
} else {
// Standalone-Modus: Normal teleportieren
if (ticket.getLocation() != null) {
player.teleport(ticket.getLocation());
} else {
player.sendMessage(plugin.color("&7Teleportation nicht möglich World nicht gefunden."));
}
}
});
});
}
@@ -228,6 +243,9 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
boolean success = plugin.getDatabaseManager().closeTicket(ticketId, comment);
if (success) {
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
// Ticket in persistente Stats-Tabelle eintragen (bleibt auch nach Löschung erhalten).
// player.getName() = der Admin der /ticket close ausgeführt hat nicht zwingend der Claimer.
if (ticket != null) plugin.getDatabaseManager().recordClosedTicket(ticket, player.getName());
Bukkit.getScheduler().runTask(plugin, () -> {
player.sendMessage(plugin.formatMessage("messages.ticket-closed").replace("{id}", String.valueOf(ticketId)));
if (ticket != null) {
@@ -252,12 +270,23 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
try { id = Integer.parseInt(args[1]); }
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
Player target = Bukkit.getPlayer(args[2]);
if (target == null) { player.sendMessage(plugin.color("&cSpieler nicht gefunden!")); return; }
// BungeeCord: Ziel-Spieler lokal suchen
Player localTarget = Bukkit.getPlayer(args[2]);
if (localTarget == null) {
if (plugin.isBungeeCordEnabled()) {
player.sendMessage(plugin.color("&7[BungeeCord] Spieler &e" + args[2]
+ " &7ist auf diesem Server nicht online."));
player.sendMessage(plugin.color("&7Tipp: Forwarden geht nur zu Spielern auf &bdemselben Server&7."));
} else {
player.sendMessage(plugin.color("&cSpieler nicht gefunden!"));
}
return;
}
final int ticketId = id;
final String fromName = player.getName();
final Player t = target;
final Player t = localTarget;
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
boolean success = plugin.getDatabaseManager().forwardTicket(ticketId, t.getUniqueId(), t.getName());
@@ -295,7 +324,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
return;
}
// Spieler darf nur auf eigene Tickets kommentieren (Supporter/Admin: alle)
boolean isOwner = ticket.getCreatorUUID().equals(player.getUniqueId());
boolean isStaff = player.hasPermission("ticket.support") || player.hasPermission("ticket.admin");
if (!isOwner && !isStaff) {
@@ -309,7 +337,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
Bukkit.getScheduler().runTask(plugin, () -> {
if (success) {
player.sendMessage(plugin.color("&aDein Kommentar zu Ticket &e#" + ticketId + " &awurde gespeichert."));
// Supporter/Admin und Ticket-Ersteller benachrichtigen
notifyCommentReceivers(player, ticket, message);
} else {
player.sendMessage(plugin.color("&cFehler beim Speichern des Kommentars."));
@@ -318,42 +345,78 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
});
}
/**
* Benachrichtigt alle relevanten Empfänger über einen neuen Kommentar.
*
* ── BUG FIX #2 ──────────────────────────────────────────────────────────
* Vorher: broadcastTeamNotification() wurde am Ende ZUSÄTZLICH aufgerufen
* obwohl alle lokalen Supporter bereits einzeln per Schleife
* benachrichtigt wurden. Das führte zu:
* a) Doppelter Nachricht für lokale Supporter
* b) broadcastTeamNotification() sendet intern ebenfalls lokal →
* lokale Supporter sahen die Nachricht dreifach
* c) Das Forward-Paket an andere Server war korrekt, aber die
* Empfänger auf anderen Servern sahen auch Duplikate da
* broadcastTeamNotification() wiederum lokal sendet
*
* Fix: broadcastTeamNotification() ERSETZT die lokale Supporter-Schleife
* komplett. Die Methode sendet bereits lokal direkt und forwardet
* gleichzeitig an alle anderen BungeeCord-Server.
* Im Standalone-Modus bleibt die lokale Schleife erhalten.
* ────────────────────────────────────────────────────────────────────────
*/
private void notifyCommentReceivers(Player author, Ticket ticket, String message) {
String onlineMsg = plugin.color("&e[Ticket #" + ticket.getId() + "] &f" + author.getName() + " &7hat kommentiert: &f" + message);
String offlineMsg = "&e[Ticket #" + ticket.getId() + "] &f" + author.getName() + " &7hat kommentiert (während du offline warst): &f" + message;
// Ticket-Ersteller benachrichtigen (wenn nicht der Autor selbst)
// ── 1. Ticket-Ersteller benachrichtigen (wenn nicht der Autor selbst) ──
if (!ticket.getCreatorUUID().equals(author.getUniqueId())) {
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
if (creator != null && creator.isOnline()) {
creator.sendMessage(onlineMsg);
} else if (plugin.isBungeeCordEnabled()) {
// BungeeCord: Zustellung via Plugin-Messaging, kein Pending-Eintrag
// (PlayerJoinListener übernimmt Offline-Fallback via close_notified-Logik)
plugin.getBungeeMessenger().sendMessageToPlayer(
ticket.getCreatorUUID(), ticket.getCreatorName(), onlineMsg);
} else {
// Offline → für nächsten Login speichern
// Standalone: Offline → für nächsten Login speichern
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
plugin.getDatabaseManager().addPendingNotification(ticket.getCreatorUUID(), offlineMsg));
}
}
// Supporter/Admin benachrichtigen (wenn Autor kein Supporter ist)
// ── 2. Supporter/Admin benachrichtigen (wenn Kommentar vom Spieler kommt) ──
if (!author.hasPermission("ticket.support") && !author.hasPermission("ticket.admin")) {
// Claimer des Tickets bevorzugt benachrichtigen
UUID claimerUUID = ticket.getClaimerUUID();
if (claimerUUID != null && !claimerUUID.equals(author.getUniqueId())) {
Player claimer = Bukkit.getPlayer(claimerUUID);
if (claimer != null && claimer.isOnline()) {
claimer.sendMessage(onlineMsg);
} else {
String claimerOffline = "&e[Ticket #" + ticket.getId() + "] &f" + author.getName() + " &7hat auf dein bearbeitetes Ticket kommentiert (offline): &f" + message;
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
plugin.getDatabaseManager().addPendingNotification(claimerUUID, claimerOffline));
if (plugin.isBungeeCordEnabled()) {
// BungeeCord-Modus: broadcastTeamNotification() übernimmt ALLES
// lokal direkt + Forward an alle anderen Server in einem Paket.
// KEINE zusätzliche lokale Schleife, da das zu Duplikaten führt.
plugin.getBungeeMessenger().broadcastTeamNotification(onlineMsg);
} else {
// Standalone-Modus: Claimer gezielt benachrichtigen
UUID claimerUUID = ticket.getClaimerUUID();
if (claimerUUID != null && !claimerUUID.equals(author.getUniqueId())) {
Player claimer = Bukkit.getPlayer(claimerUUID);
if (claimer != null && claimer.isOnline()) {
claimer.sendMessage(onlineMsg);
} else {
String claimerOffline = "&e[Ticket #" + ticket.getId() + "] &f" + author.getName()
+ " &7hat auf dein bearbeitetes Ticket kommentiert (offline): &f" + message;
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
plugin.getDatabaseManager().addPendingNotification(claimerUUID, claimerOffline));
}
}
}
// Alle anderen Online-Supporter zusätzlich informieren
for (Player p : Bukkit.getOnlinePlayers()) {
if (p.getUniqueId().equals(author.getUniqueId())) continue;
if (claimerUUID != null && p.getUniqueId().equals(claimerUUID)) continue; // schon oben
if (p.hasPermission("ticket.support") || p.hasPermission("ticket.admin")) {
p.sendMessage(onlineMsg);
// Alle anderen Online-Supporter auf diesem Server informieren
for (Player p : Bukkit.getOnlinePlayers()) {
if (p.getUniqueId().equals(author.getUniqueId())) continue;
if (claimerUUID != null && p.getUniqueId().equals(claimerUUID)) continue;
if (p.hasPermission("ticket.support") || p.hasPermission("ticket.admin")) {
p.sendMessage(onlineMsg);
}
}
}
}
@@ -468,7 +531,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
player.sendMessage(plugin.color("&7Keine gesperrten Spieler."));
} else {
for (String[] entry : list) {
// {uuid, name, reason, bannedBy, bannedAt}
player.sendMessage(plugin.color("&e" + entry[1] + " &7 &f" + entry[2]
+ " &7(gesperrt von &e" + entry[3] + "&7)"));
}
@@ -488,6 +550,9 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
plugin.reloadConfig();
plugin.getCategoryManager().reload();
player.sendMessage(plugin.color("&aKonfiguration wurde neu geladen. &7(inkl. Kategorien)"));
if (plugin.isBungeeCordEnabled()) {
player.sendMessage(plugin.color("&8[BungeeCord] &7Server: &b" + plugin.getServerName()));
}
}
// ─────────────────────────── /ticket archive ───────────────────────────
@@ -508,26 +573,55 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
private void handleStats(Player player) {
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
var stats = plugin.getDatabaseManager().getTicketStats();
var stats = plugin.getDatabaseManager().getTicketStats();
var staffRatings = plugin.getDatabaseManager().getStaffRatings();
Bukkit.getScheduler().runTask(plugin, () -> {
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Ticket Statistik"));
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&eGesamt: &a" + stats.total));
player.sendMessage(plugin.color("&eOffen: &a" + stats.open));
player.sendMessage(plugin.color("&eGeschlossen: &a" + stats.closed));
player.sendMessage(plugin.color("&eGesamt: &a" + stats.total));
player.sendMessage(plugin.color("&eOffen: &a" + stats.open));
player.sendMessage(plugin.color("&eGeschlossen: &a" + stats.closed + " &7(historisch)"));
player.sendMessage(plugin.color("&eWeitergeleitet: &a" + stats.forwarded));
if (plugin.getConfig().getBoolean("rating-enabled", true)) {
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Support-Bewertungen"));
player.sendMessage(plugin.color("&6Support-Bewertungen &7(gesamt, historisch)"));
player.sendMessage(plugin.color("&a👍 Positiv: &f" + stats.thumbsUp
+ " &c👎 Negativ: &f" + stats.thumbsDown));
int total = stats.thumbsUp + stats.thumbsDown;
if (total > 0) {
int percent = (int) Math.round(stats.thumbsUp * 100.0 / total);
int totalRated = stats.thumbsUp + stats.thumbsDown;
if (totalRated > 0) {
int percent = (int) Math.round(stats.thumbsUp * 100.0 / totalRated);
player.sendMessage(plugin.color("&7Zufriedenheit: &e" + percent + "%"));
}
// Bewertungen pro Support-Mitarbeiter
if (!staffRatings.isEmpty()) {
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Bewertungen nach Support-Mitarbeiter:"));
player.sendMessage(plugin.color("&7 Name 👍 👎 Tickets Zufrieden"));
for (String[] row : staffRatings) {
// row: [name, up, down, totalClosed, percent]
String name = String.format("%-16s", row[0]);
String up = String.format("%-5s", row[1]);
String down = String.format("%-5s", row[2]);
String total = String.format("%-8s", row[3]);
String percent = row[4];
player.sendMessage(plugin.color(
"&e " + name + " &a" + up + " &c" + down + " &7" + total + " &e" + percent));
}
}
}
// BungeeCord: Tickets pro Server anzeigen
if (plugin.isBungeeCordEnabled() && !stats.byServer.isEmpty()) {
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Tickets nach Server:"));
stats.byServer.entrySet().stream()
.sorted((a, b) -> b.getValue() - a.getValue())
.forEach(e -> player.sendMessage(plugin.color("&b " + e.getKey() + ": &a" + e.getValue())));
}
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Top Ersteller:"));
stats.byPlayer.entrySet().stream()
@@ -625,8 +719,7 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
});
}
/** Parst Benutzer-Eingaben wie "high", "hoch", "urgent", "dringend" etc. zu TicketPriority.
* Gibt null zurück wenn keine Übereinstimmung. */
/** Parst Benutzer-Eingaben zu TicketPriority. Gibt null zurück wenn keine Übereinstimmung. */
private TicketPriority parsePriority(String input) {
if (input == null) return null;
return switch (input.toLowerCase()) {
@@ -661,14 +754,12 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
&& plugin.getConfig().getBoolean("categories-enabled", true)) {
for (ConfigCategory c : plugin.getCategoryManager().getAll())
if (c.getKey().startsWith(args[1].toLowerCase())) completions.add(c.getKey());
// auch Priorität direkt ohne Kategorie anbieten
if (plugin.getConfig().getBoolean("priorities-enabled", true))
for (String p : List.of("low", "normal", "high", "urgent"))
if (p.startsWith(args[1].toLowerCase())) completions.add(p);
} else if (args.length == 3 && args[0].equalsIgnoreCase("create")
&& plugin.getConfig().getBoolean("priorities-enabled", true)) {
// Priorität nach Kategorie
for (String p : List.of("low", "normal", "high", "urgent"))
if (p.startsWith(args[2].toLowerCase())) completions.add(p);
@@ -677,6 +768,7 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
if (p.startsWith(args[2].toLowerCase())) completions.add(p);
} else if (args.length == 3 && args[0].equalsIgnoreCase("forward")) {
// BungeeCord: Nur lokal online Spieler als Tab-Completion
for (Player p : Bukkit.getOnlinePlayers())
if (p.getName().toLowerCase().startsWith(args[2].toLowerCase())) completions.add(p.getName());