376 lines
19 KiB
Java
376 lines
19 KiB
Java
package de.ticketsystem.manager;
|
||
|
||
import de.ticketsystem.TicketPlugin;
|
||
import de.ticketsystem.model.ConfigCategory;
|
||
import de.ticketsystem.model.Ticket;
|
||
import de.ticketsystem.model.TicketStatus;
|
||
import org.bukkit.Bukkit;
|
||
import org.bukkit.entity.Player;
|
||
|
||
import java.util.HashMap;
|
||
import java.util.Map;
|
||
import java.util.UUID;
|
||
|
||
public class TicketManager {
|
||
|
||
private final TicketPlugin plugin;
|
||
|
||
/** Cooldown Map: UUID → Zeitstempel letztes Ticket */
|
||
private final Map<UUID, Long> cooldowns = new HashMap<>();
|
||
|
||
public TicketManager(TicketPlugin plugin) {
|
||
this.plugin = plugin;
|
||
}
|
||
|
||
// ─────────────────────────── Cooldown ──────────────────────────────────
|
||
|
||
public boolean hasCooldown(UUID uuid) {
|
||
if (!cooldowns.containsKey(uuid)) return false;
|
||
long cooldownSeconds = plugin.getConfig().getLong("ticket-cooldown", 60) * 1000L;
|
||
return (System.currentTimeMillis() - cooldowns.get(uuid)) < cooldownSeconds;
|
||
}
|
||
|
||
public long getRemainingCooldown(UUID uuid) {
|
||
long cooldownMillis = plugin.getConfig().getLong("ticket-cooldown", 60) * 1000L;
|
||
long elapsed = System.currentTimeMillis() - cooldowns.getOrDefault(uuid, 0L);
|
||
return Math.max(0, (cooldownMillis - elapsed) / 1000);
|
||
}
|
||
|
||
public void setCooldown(UUID uuid) { cooldowns.put(uuid, System.currentTimeMillis()); }
|
||
|
||
// ─────────────────────────── Benachrichtigungen ────────────────────────
|
||
|
||
/**
|
||
* Benachrichtigt alle Supporter/Admins über ein neues Ticket – auch auf anderen Servern.
|
||
*
|
||
* Lokal online Spieler werden direkt angesprochen.
|
||
* Über BungeeCord werden alle anderen Server im Netzwerk ebenfalls benachrichtigt.
|
||
* Optional sendet der Discord-Webhook eine Nachricht.
|
||
*/
|
||
public void notifyTeam(Ticket ticket) {
|
||
String creatorName = ticket.getCreatorName() != null ? ticket.getCreatorName() : "Unbekannt";
|
||
String message = ticket.getMessage() != null ? ticket.getMessage() : "";
|
||
|
||
// Kategorie & Priorität optional anzeigen
|
||
String categoryInfo = "";
|
||
String priorityInfo = "";
|
||
if (plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||
ConfigCategory cat = plugin.getCategoryManager().fromKey(ticket.getCategoryKey());
|
||
categoryInfo = " §7[§r" + cat.getColored() + "§7]";
|
||
}
|
||
if (plugin.getConfig().getBoolean("priorities-enabled", true)) {
|
||
priorityInfo = " §7Priorität: §r" + ticket.getPriority().getColored();
|
||
}
|
||
|
||
// BungeeCord: Server-Herkunft anzeigen wenn BungeeCord aktiviert
|
||
String serverInfo = "";
|
||
if (plugin.isBungeeCordEnabled() && !"unknown".equals(ticket.getServerName())) {
|
||
serverInfo = " §7Server: §b" + ticket.getServerName();
|
||
}
|
||
|
||
String msg = plugin.formatMessage("messages.new-ticket-notify")
|
||
.replace("{player}", creatorName)
|
||
.replace("{message}", message)
|
||
.replace("{id}", String.valueOf(ticket.getId()))
|
||
+ categoryInfo + priorityInfo + serverInfo;
|
||
|
||
String guiHint = plugin.color("&7» Klicke &e/ticket list &7um die GUI zu öffnen.");
|
||
|
||
if (plugin.isBungeeCordEnabled()) {
|
||
// ─ BungeeCord-Modus: Team-Broadcast über alle Server ─────────────────
|
||
// BungeeMessenger sendet lokal direkt, dann per Forward an alle anderen Server.
|
||
// Beide Nachrichten werden zu einer zusammengefasst um ein einzelnes
|
||
// Forward-Paket zu erzeugen statt zwei (reduziert Netzwerklast und
|
||
// verhindert mögliche Reihenfolge-Probleme).
|
||
plugin.getBungeeMessenger().broadcastTeamNotification(msg + "\n" + guiHint);
|
||
} else {
|
||
// ─ Standalone-Modus: Nur lokal ───────────────────────────────
|
||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||
if (p.hasPermission("ticket.support") || p.hasPermission("ticket.admin")) {
|
||
p.sendMessage(msg);
|
||
p.sendMessage(guiHint);
|
||
}
|
||
}
|
||
}
|
||
|
||
plugin.getDiscordWebhook().sendNewTicket(ticket);
|
||
}
|
||
|
||
/**
|
||
* Benachrichtigt den Ersteller, wenn sein Ticket angenommen wurde.
|
||
* Setzt claimer_notified = true und persistiert es.
|
||
*
|
||
* BungeeCord: Zustellung auch wenn der Spieler auf einem anderen Server ist.
|
||
*/
|
||
public void notifyCreatorClaimed(Ticket ticket) {
|
||
String claimerName = resolveClaimerName(ticket);
|
||
|
||
String msg = plugin.formatMessage("messages.ticket-claimed-notify")
|
||
.replace("{id}", String.valueOf(ticket.getId()))
|
||
.replace("{claimer}", claimerName);
|
||
|
||
deliverToPlayer(ticket.getCreatorUUID(), ticket.getCreatorName(), msg);
|
||
|
||
// Persistiert setzen, damit Join-Listener weiß, dass Spieler bereits informiert ist
|
||
plugin.getDatabaseManager().markClaimerNotified(ticket.getId());
|
||
}
|
||
|
||
/**
|
||
* Wird beim Server-Join aufgerufen – informiert den Spieler über Tickets,
|
||
* die geclaimt oder weitergeleitet wurden während er offline war.
|
||
*/
|
||
public void notifyClaimedWhileOffline(Player player) {
|
||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||
var tickets = plugin.getDatabaseManager().getTicketsByStatus(
|
||
TicketStatus.CLAIMED, TicketStatus.FORWARDED);
|
||
|
||
for (Ticket t : tickets) {
|
||
if (!t.getCreatorUUID().equals(player.getUniqueId())) continue;
|
||
if (t.isClaimerNotified()) continue;
|
||
|
||
String claimerName = t.getClaimerName() != null ? t.getClaimerName() : "Support";
|
||
final String name = claimerName;
|
||
|
||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||
if (!player.isOnline()) return;
|
||
if (t.getStatus() == TicketStatus.CLAIMED) {
|
||
String msg = plugin.formatMessage("messages.ticket-claimed-notify")
|
||
.replace("{id}", String.valueOf(t.getId()))
|
||
.replace("{claimer}", name);
|
||
player.sendMessage(msg);
|
||
} else {
|
||
String forwardedTo = t.getForwardedToName() != null ? t.getForwardedToName() : "einen Supporter";
|
||
String msg = plugin.formatMessage("messages.ticket-forwarded-creator-notify")
|
||
.replace("{id}", String.valueOf(t.getId()))
|
||
.replace("{supporter}", forwardedTo);
|
||
player.sendMessage(msg);
|
||
}
|
||
});
|
||
|
||
plugin.getDatabaseManager().markClaimerNotified(t.getId());
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Benachrichtigt den Ersteller, wenn sein Ticket weitergeleitet wurde.
|
||
* BungeeCord: Cross-Server-Zustellung.
|
||
*/
|
||
public void notifyCreatorForwarded(Ticket ticket) {
|
||
String forwardedTo = ticket.getForwardedToName() != null ? ticket.getForwardedToName() : "einen Supporter";
|
||
String msg = plugin.formatMessage("messages.ticket-forwarded-creator-notify")
|
||
.replace("{id}", String.valueOf(ticket.getId()))
|
||
.replace("{supporter}", forwardedTo);
|
||
|
||
deliverToPlayer(ticket.getCreatorUUID(), ticket.getCreatorName(), msg);
|
||
|
||
// Auch bei Weiterleitung notified setzen
|
||
plugin.getDatabaseManager().markClaimerNotified(ticket.getId());
|
||
}
|
||
|
||
/**
|
||
* Sendet dem weitergeleiteten Supporter eine Benachrichtigung.
|
||
* BungeeCord: Zustellung auch wenn der Supporter auf einem anderen Server ist.
|
||
*/
|
||
public void notifyForwardedTo(Ticket ticket, String fromName) {
|
||
if (ticket.getForwardedToUUID() == null) return;
|
||
|
||
String creatorName = ticket.getCreatorName() != null ? ticket.getCreatorName() : "Unbekannt";
|
||
String msg = plugin.formatMessage("messages.ticket-forwarded-notify")
|
||
.replace("{player}", creatorName)
|
||
.replace("{id}", String.valueOf(ticket.getId()));
|
||
|
||
deliverToPlayer(ticket.getForwardedToUUID(), ticket.getForwardedToName(), msg);
|
||
|
||
plugin.getDiscordWebhook().sendTicketForwarded(ticket, fromName);
|
||
}
|
||
|
||
/**
|
||
* Benachrichtigt den Ersteller, wenn sein Ticket geschlossen wurde.
|
||
* BungeeCord: Cross-Server-Zustellung + Fallback in Pending-DB.
|
||
*/
|
||
public void notifyCreatorClosed(Ticket ticket) { notifyCreatorClosed(ticket, null); }
|
||
|
||
public void notifyCreatorClosed(Ticket ticket, String closerName) {
|
||
// Bug-Fix: close_notified wird in der DB gespeichert – kein In-Memory-Set mehr.
|
||
// Dadurch funktioniert der Check auch nach einem Server-Wechsel korrekt.
|
||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||
plugin.getDatabaseManager().markCloseNotified(ticket.getId()));
|
||
|
||
String comment = (ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty())
|
||
? ticket.getCloseComment() : "";
|
||
|
||
// Hauptnachricht
|
||
String msg = plugin.formatMessage("messages.ticket-closed-notify")
|
||
.replace("{id}", String.valueOf(ticket.getId()))
|
||
.replace("{comment}", comment);
|
||
|
||
// Bewertungsaufforderung
|
||
String ratingMsg = null;
|
||
if (plugin.getConfig().getBoolean("rating-enabled", true)) {
|
||
ratingMsg = plugin.color(
|
||
"&8&m &r\n" +
|
||
"&6Wie zufrieden bist du mit dem Support?\n" +
|
||
"&a/ticket rate " + ticket.getId() + " good &7– 👍 Gut\n" +
|
||
"&c/ticket rate " + ticket.getId() + " bad &7– 👎 Schlecht\n" +
|
||
"&8&m ");
|
||
}
|
||
|
||
// Prüfen ob Ersteller lokal online ist
|
||
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
|
||
if (creator != null && creator.isOnline()) {
|
||
// ─ Lokal online: direkt zustellen ────────────────────────────
|
||
creator.sendMessage(msg);
|
||
if (!comment.isEmpty())
|
||
creator.sendMessage(plugin.color("&7Kommentar des Supports: &f" + comment));
|
||
if (ratingMsg != null) creator.sendMessage(ratingMsg);
|
||
|
||
} else if (plugin.isBungeeCordEnabled()) {
|
||
// ─ BungeeCord: via Plugin-Messaging auf anderen Servern zustellen ─
|
||
// KEIN savePendingClosedNotification hier! Das würde bei Server-Wechsel
|
||
// als "Offline-Nachricht" doppelt angezeigt werden.
|
||
// BungeeCord's "Message"-Kanal erreicht den Spieler netzwerkweit sofern er online ist.
|
||
// Ist er wirklich offline, sieht er beim nächsten Login via PlayerJoinListener
|
||
// eine frische Benachrichtigung (close_notified=true verhindert Duplikate).
|
||
plugin.getBungeeMessenger().sendMessageToPlayer(
|
||
ticket.getCreatorUUID(), ticket.getCreatorName(), msg);
|
||
if (!comment.isEmpty())
|
||
plugin.getBungeeMessenger().sendMessageToPlayer(
|
||
ticket.getCreatorUUID(), ticket.getCreatorName(),
|
||
plugin.color("&7Kommentar des Supports: &f" + comment));
|
||
if (ratingMsg != null)
|
||
plugin.getBungeeMessenger().sendMessageToPlayer(
|
||
ticket.getCreatorUUID(), ticket.getCreatorName(), ratingMsg);
|
||
|
||
} else {
|
||
// ─ Standalone, Spieler offline: in Pending-DB speichern ──────
|
||
savePendingClosedNotification(ticket, comment);
|
||
}
|
||
|
||
String closer = closerName != null ? closerName : "Unbekannt";
|
||
plugin.getDiscordWebhook().sendTicketClosed(ticket, closer);
|
||
}
|
||
|
||
/**
|
||
* Bug-Fix: Nutzt jetzt close_notified aus der DB statt ein In-Memory-Set.
|
||
* Funktioniert damit auch nach Server-Wechseln in BungeeCord-Netzwerken korrekt.
|
||
*
|
||
* @deprecated Bitte stattdessen ticket.isCloseNotified() direkt prüfen,
|
||
* da das Ticket-Objekt aus der DB bereits den korrekten Wert hat.
|
||
*/
|
||
public boolean wasClosedNotificationSent(int ticketId) {
|
||
// Direkt in der DB nachschlagen – kein In-Memory-Set, kein Server-gebundener State
|
||
Ticket t = plugin.getDatabaseManager().getTicketById(ticketId);
|
||
return t != null && t.isCloseNotified();
|
||
}
|
||
|
||
// ─────────────────────────── BungeeCord Hilfsmethoden ──────────────────
|
||
|
||
// ── BUG FIX #2 ──────────────────────────────────────────────────────────
|
||
// Vorher: addPendingNotification() wurde IMMER asynchron ausgeführt –
|
||
// auch wenn der Spieler lokal online war oder BungeeCord die
|
||
// Nachricht bereits zugestellt hat. Das führte dazu, dass Spieler
|
||
// beim nächsten Login immer noch eine "verpasste Nachricht" sahen,
|
||
// obwohl sie die Nachricht bereits erhalten hatten.
|
||
//
|
||
// Fix: addPendingNotification() wird nur noch aufgerufen wenn:
|
||
// 1. Der Spieler NICHT lokal online ist, UND
|
||
// 2. BungeeCord NICHT aktiviert ist (Standalone-Fallback).
|
||
// Im BungeeCord-Modus ist der BungeeCord-"Message"-Kanal für die
|
||
// Zustellung zuständig. Offline-Spieler werden über close_notified
|
||
// und den PlayerJoinListener beim nächsten Login benachrichtigt.
|
||
// ────────────────────────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Zustellung einer Nachricht an einen Spieler.
|
||
*
|
||
* Ablauf:
|
||
* 1. Spieler lokal online → direkt
|
||
* 2. BungeeCord aktiv → via Plugin-Messaging (kein Pending-Eintrag)
|
||
* 3. Offline + Standalone → Pending-DB (Zustellung beim nächsten Login)
|
||
*
|
||
* @param uuid UUID des Empfängers
|
||
* @param name Spielername (für BungeeCord-Lookup)
|
||
* @param message Bereits color-übersetzter Text
|
||
*/
|
||
private void deliverToPlayer(UUID uuid, String name, String message) {
|
||
Player local = Bukkit.getPlayer(uuid);
|
||
if (local != null && local.isOnline()) {
|
||
// Lokal online → direkt zustellen, fertig
|
||
local.sendMessage(message);
|
||
return;
|
||
}
|
||
|
||
if (plugin.isBungeeCordEnabled()) {
|
||
// BungeeCord-Modus: Nachricht über Plugin-Messaging weiterleiten.
|
||
// KEIN Pending-Eintrag! BungeeCord übernimmt die Zustellung.
|
||
// Ist der Spieler wirklich offline, kümmert sich der PlayerJoinListener
|
||
// beim nächsten Login um die Benachrichtigung.
|
||
plugin.getBungeeMessenger().sendMessageToPlayer(uuid, name, message);
|
||
return;
|
||
}
|
||
|
||
// Standalone-Modus, Spieler offline → in Pending-DB speichern
|
||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||
plugin.getDatabaseManager().addPendingNotification(uuid, message));
|
||
}
|
||
|
||
/**
|
||
* Speichert eine ausstehende Schließ-Benachrichtigung in der DB.
|
||
*/
|
||
private void savePendingClosedNotification(Ticket ticket, String comment) {
|
||
String pendingMsg = "&e[Ticket #" + ticket.getId() + "] &7Dein Ticket wurde geschlossen."
|
||
+ (comment.isEmpty() ? "" : " &7Kommentar: &f" + comment)
|
||
+ (plugin.getConfig().getBoolean("rating-enabled", true)
|
||
? " &7Bewertung: &e/ticket rate " + ticket.getId() + " good/bad" : "");
|
||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||
plugin.getDatabaseManager().addPendingNotification(ticket.getCreatorUUID(), pendingMsg));
|
||
}
|
||
|
||
// ─────────────────────────── Hilfsmethoden ─────────────────────────────
|
||
|
||
private String resolveClaimerName(Ticket ticket) {
|
||
if (ticket.getClaimerName() != null) return ticket.getClaimerName();
|
||
if (ticket.getClaimerUUID() != null) {
|
||
String name = Bukkit.getOfflinePlayer(ticket.getClaimerUUID()).getName();
|
||
if (name != null) return name;
|
||
}
|
||
return "Support";
|
||
}
|
||
|
||
public boolean hasReachedTicketLimit(UUID uuid) {
|
||
int max = plugin.getConfig().getInt("max-open-tickets-per-player", 2);
|
||
if (max <= 0) return false;
|
||
return plugin.getDatabaseManager().countOpenTicketsByPlayer(uuid) >= max;
|
||
}
|
||
|
||
public void sendHelpMessage(Player player) {
|
||
player.sendMessage(plugin.color("&8&m "));
|
||
player.sendMessage(plugin.color("&6TicketSystem &7– Befehle"));
|
||
player.sendMessage(plugin.color("&8&m "));
|
||
player.sendMessage(plugin.color("&e/ticket create [Kategorie] <Text> &7– Neues Ticket erstellen"));
|
||
player.sendMessage(plugin.color("&e/ticket list &7– Deine Tickets ansehen (GUI)"));
|
||
player.sendMessage(plugin.color("&e/ticket comment <ID> <Text> &7– Nachricht zu einem Ticket"));
|
||
|
||
if (plugin.getConfig().getBoolean("rating-enabled", true))
|
||
player.sendMessage(plugin.color("&e/ticket rate <ID> <good|bad> &7– Support bewerten"));
|
||
|
||
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) {
|
||
player.sendMessage(plugin.color("&e/ticket claim <ID> &7– Ticket annehmen"));
|
||
player.sendMessage(plugin.color("&e/ticket close <ID> [Kommentar] &7– Ticket schließen"));
|
||
}
|
||
if (player.hasPermission("ticket.admin")) {
|
||
player.sendMessage(plugin.color("&e/ticket forward <ID> <Spieler> &7– Ticket weiterleiten"));
|
||
player.sendMessage(plugin.color("&e/ticket blacklist <add|remove|list> [Spieler] [Grund] &7– Blacklist verwalten"));
|
||
player.sendMessage(plugin.color("&e/ticket reload &7– Konfiguration neu laden"));
|
||
player.sendMessage(plugin.color("&e/ticket stats &7– Statistiken anzeigen"));
|
||
}
|
||
player.sendMessage(plugin.color("&8&m "));
|
||
|
||
// BungeeCord-Status anzeigen
|
||
if (player.hasPermission("ticket.admin") && plugin.isBungeeCordEnabled()) {
|
||
player.sendMessage(plugin.color("&8[BungeeCord] &7Server: &b" + plugin.getServerName()
|
||
+ " &8| Cross-Server-Benachrichtigungen &aaktiv"));
|
||
}
|
||
}
|
||
} |