Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-21 17:49:01 +02:00
parent 31c3496519
commit 03b484eac5
7 changed files with 140 additions and 127 deletions

View File

@@ -120,15 +120,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
} }
// ── /ticket create (Konsole) ────────────────────────────────────────── // ── /ticket create (Konsole) ──────────────────────────────────────────
//
// Syntax: /ticket create <Spielername> [Kategorie] [Priorität] <Beschreibung>
//
// Das Ticket wird im Namen des angegebenen Spielers erstellt.
// Als Erstell-Position wird die aktuelle Spieler-Location verwendet
// (oder eine Default-Location wenn der Spieler offline ist).
private void handleCreateConsole(CommandSender console, String[] args) { private void handleCreateConsole(CommandSender console, String[] args) {
// args[0] = "create", args[1] = Spielername, args[2..] = [Kategorie] [Priorität] Text
if (args.length < 3) { if (args.length < 3) {
console.sendMessage("[TicketSystem] Verwendung: /ticket create <Spielername> [Kategorie] [Priorität] <Beschreibung>"); console.sendMessage("[TicketSystem] Verwendung: /ticket create <Spielername> [Kategorie] [Priorität] <Beschreibung>");
console.sendMessage("[TicketSystem] Beispiel: /ticket create Notch bug high Spieler fällt durch den Boden"); console.sendMessage("[TicketSystem] Beispiel: /ticket create Notch bug high Spieler fällt durch den Boden");
@@ -137,7 +130,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
String targetName = args[1]; String targetName = args[1];
// Spieler suchen (online bevorzugt, sonst OfflinePlayer)
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
OfflinePlayer offlineTarget = Bukkit.getOfflinePlayer(targetName); OfflinePlayer offlineTarget = Bukkit.getOfflinePlayer(targetName);
UUID targetUUID = offlineTarget.getUniqueId(); UUID targetUUID = offlineTarget.getUniqueId();
@@ -146,12 +138,11 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
CategoryManager cm = plugin.getCategoryManager(); CategoryManager cm = plugin.getCategoryManager();
ConfigCategory category = cm.getDefault(); ConfigCategory category = cm.getDefault();
TicketPriority priority = TicketPriority.NORMAL; TicketPriority priority = TicketPriority.NORMAL;
int msgStart = 2; // Index ab dem die Beschreibung beginnt (relativ zu args[2..]) int msgStart = 2;
boolean categoriesOn = plugin.getConfig().getBoolean("categories-enabled", true); boolean categoriesOn = plugin.getConfig().getBoolean("categories-enabled", true);
boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true); boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true);
// args[2] = optional Kategorie, args[3] = optional Priorität, Rest = Text
if (args.length >= 4 && categoriesOn) { if (args.length >= 4 && categoriesOn) {
ConfigCategory parsedCat = cm.resolve(args[2]); ConfigCategory parsedCat = cm.resolve(args[2]);
if (parsedCat != null) { if (parsedCat != null) {
@@ -170,15 +161,15 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
if (parsedPrio != null) { priority = parsedPrio; msgStart = 3; } if (parsedPrio != null) { priority = parsedPrio; msgStart = 3; }
} }
// Beschreibung zusammensetzen (args[msgStart+1] weil args[1]=Spielername) // msgStart ist der absolute Index in args[] ab dem die Beschreibung beginnt.
int absoluteStart = msgStart + 1; // +1 für den Spielernamen in args[1] // Kein +1 nötig msgStart zeigt bereits auf das erste Beschreibungs-Wort.
if (absoluteStart >= args.length) { if (msgStart >= args.length) {
console.sendMessage("[TicketSystem] Fehler: Keine Beschreibung angegeben."); console.sendMessage("[TicketSystem] Fehler: Keine Beschreibung angegeben.");
console.sendMessage("[TicketSystem] Verwendung: /ticket create <Spielername> [Kategorie] [Priorität] <Beschreibung>"); console.sendMessage("[TicketSystem] Verwendung: /ticket create <Spielername> [Kategorie] [Priorität] <Beschreibung>");
return; return;
} }
String message = String.join(" ", Arrays.copyOfRange(args, absoluteStart, args.length)); String message = String.join(" ", Arrays.copyOfRange(args, msgStart, args.length));
int maxLen = plugin.getConfig().getInt("max-description-length", 100); int maxLen = plugin.getConfig().getInt("max-description-length", 100);
if (message.isEmpty()) { if (message.isEmpty()) {
@@ -190,7 +181,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
return; return;
} }
// Location: online-Spieler → aktuelle Pos; offline → Spawn der Default-Welt
Location location; Location location;
Player onlineTarget = Bukkit.getPlayer(targetUUID); Player onlineTarget = Bukkit.getPlayer(targetUUID);
if (onlineTarget != null) { if (onlineTarget != null) {
@@ -233,7 +223,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
+ "' erstellt." + catInfo + prioInfo); + "' erstellt." + catInfo + prioInfo);
console.sendMessage("[TicketSystem] Nachricht: " + fMessage); console.sendMessage("[TicketSystem] Nachricht: " + fMessage);
// Team benachrichtigen (gleich wie bei normalem /ticket create)
plugin.getTicketManager().notifyTeam(ticket); plugin.getTicketManager().notifyTeam(ticket);
}); });
}); });
@@ -247,9 +236,10 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
plugin.reloadSettings(); plugin.reloadSettings();
plugin.getTicketGUI().reloadConfig(); plugin.getTicketGUI().reloadConfig();
plugin.getTicketGUI().reloadTitles(); plugin.getTicketGUI().reloadTitles();
plugin.getFaqGUI().reloadConfig(); // FaqGUI ist null wenn faq-enabled: false → Null-Check erforderlich
if (plugin.getFaqGUI() != null) plugin.getFaqGUI().reloadConfig();
plugin.getCategoryManager().reload(); plugin.getCategoryManager().reload();
plugin.getFaqManager().reload(); if (plugin.getFaqManager() != null) plugin.getFaqManager().reload();
plugin.getTicketCache().clear(); plugin.getTicketCache().clear();
console.sendMessage("[TicketSystem] Konfiguration erfolgreich neu geladen."); console.sendMessage("[TicketSystem] Konfiguration erfolgreich neu geladen.");
} }
@@ -729,9 +719,10 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
plugin.reloadSettings(); plugin.reloadSettings();
plugin.getTicketGUI().reloadConfig(); plugin.getTicketGUI().reloadConfig();
plugin.getTicketGUI().reloadTitles(); plugin.getTicketGUI().reloadTitles();
plugin.getFaqGUI().reloadConfig(); // FaqGUI ist null wenn faq-enabled: false → Null-Check erforderlich
if (plugin.getFaqGUI() != null) plugin.getFaqGUI().reloadConfig();
plugin.getCategoryManager().reload(); plugin.getCategoryManager().reload();
plugin.getFaqManager().reload(); if (plugin.getFaqManager() != null) plugin.getFaqManager().reload();
plugin.getTicketCache().clear(); plugin.getTicketCache().clear();
player.sendMessage(plugin.lang().get("reload.success")); player.sendMessage(plugin.lang().get("reload.success"));
if (plugin.isBungeeCordEnabled()) if (plugin.isBungeeCordEnabled())
@@ -944,14 +935,13 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
// ── /ticket faq ─────────────────────────────────────────────────────── // ── /ticket faq ───────────────────────────────────────────────────────
private void handleFaq(Player player, String[] args) { private void handleFaq(Player player, String[] args) {
// FAQ-System global deaktiviert?
if (!plugin.getConfig().getBoolean("faq-enabled", true)) { if (!plugin.getConfig().getBoolean("faq-enabled", true)) {
plugin.lang().send(player, "faq.disabled"); plugin.lang().send(player, "faq.disabled");
return; return;
} }
if (args.length == 1) { if (args.length == 1) {
plugin.getFaqGUI().openFaqGUI(player); if (plugin.getFaqGUI() != null) plugin.getFaqGUI().openFaqGUI(player);
return; return;
} }
@@ -1122,7 +1112,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
if (!player.hasPermission("ticket.admin")) { if (!player.hasPermission("ticket.admin")) {
plugin.lang().send(player, "general.no-permission"); return; plugin.lang().send(player, "general.no-permission"); return;
} }
// FAQ-System deaktiviert?
if (!plugin.getConfig().getBoolean("faq-enabled", true)) { if (!plugin.getConfig().getBoolean("faq-enabled", true)) {
plugin.lang().send(player, "faq.disabled"); return; plugin.lang().send(player, "faq.disabled"); return;
} }
@@ -1218,6 +1207,7 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
} }
private List<String> getFaqCategoryKeysForTab(String input) { private List<String> getFaqCategoryKeysForTab(String input) {
if (plugin.getFaqManager() == null) return new ArrayList<>();
String lower = input == null ? "" : input.toLowerCase(); String lower = input == null ? "" : input.toLowerCase();
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
for (FaqCategory cat : plugin.getFaqManager().getAllCategories()) for (FaqCategory cat : plugin.getFaqManager().getAllCategories())
@@ -1257,11 +1247,9 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
for (String s : List.of("create", "reload", "archive", "stats")) for (String s : List.of("create", "reload", "archive", "stats"))
if (s.startsWith(args[0].toLowerCase())) completions.add(s); if (s.startsWith(args[0].toLowerCase())) completions.add(s);
} else if (args.length == 2 && normalize(args[0]).equals("create")) { } else if (args.length == 2 && normalize(args[0]).equals("create")) {
// Spielernamen vorschlagen
for (Player p : Bukkit.getOnlinePlayers()) for (Player p : Bukkit.getOnlinePlayers())
if (p.getName().toLowerCase().startsWith(args[1].toLowerCase())) completions.add(p.getName()); if (p.getName().toLowerCase().startsWith(args[1].toLowerCase())) completions.add(p.getName());
} else if (args.length == 3 && normalize(args[0]).equals("create")) { } else if (args.length == 3 && normalize(args[0]).equals("create")) {
// Kategorien vorschlagen
completions.addAll(plugin.getCategoryManager().getDisplayNamesForTabComplete(args[2])); completions.addAll(plugin.getCategoryManager().getDisplayNamesForTabComplete(args[2]));
} }
return completions; return completions;
@@ -1321,6 +1309,7 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
&& (args[1].equalsIgnoreCase("edit") || args[1].equalsIgnoreCase("delete") && (args[1].equalsIgnoreCase("edit") || args[1].equalsIgnoreCase("delete")
|| args[1].equalsIgnoreCase("bearbeiten") || args[1].equalsIgnoreCase("löschen")) || args[1].equalsIgnoreCase("bearbeiten") || args[1].equalsIgnoreCase("löschen"))
&& player.hasPermission("ticket.admin")) { && player.hasPermission("ticket.admin")) {
if (plugin.getFaqManager() != null)
for (FaqEntry e : plugin.getFaqManager().getAll()) for (FaqEntry e : plugin.getFaqManager().getAll())
completions.add(String.valueOf(e.getId())); completions.add(String.valueOf(e.getId()));

View File

@@ -32,9 +32,12 @@ public class PlayerJoinListener implements Listener {
int count = plugin.getDatabaseManager().countOpenTickets(); int count = plugin.getDatabaseManager().countOpenTickets();
if (count > 0) { if (count > 0) {
Bukkit.getScheduler().runTaskLater(plugin, () -> { Bukkit.getScheduler().runTaskLater(plugin, () -> {
player.sendMessage(plugin.lang().format("join.open-tickets", // Anzahl als normaler Text kein MiniMessage nötig
"{count}", String.valueOf(count))); plugin.lang().send(player, "join.open-tickets",
player.sendMessage(plugin.lang().get("join.open-tickets-hint")); "{count}", String.valueOf(count));
// Hint über send() routen → MiniMessage greift wenn
// <click:run_command:...> in der lang.yml steht
plugin.lang().send(player, "join.open-tickets-hint");
}, 40L); }, 40L);
} }
}); });
@@ -54,14 +57,14 @@ public class PlayerJoinListener implements Listener {
if (!player.isOnline()) return; if (!player.isOnline()) return;
World world = Bukkit.getWorld(pt.world()); World world = Bukkit.getWorld(pt.world());
if (world == null) { if (world == null) {
player.sendMessage(plugin.lang().format("join.teleport-world-missing", plugin.lang().send(player, "join.teleport-world-missing",
"{world}", pt.world())); "{world}", pt.world());
return; return;
} }
Location loc = new Location(world, pt.x(), pt.y(), pt.z(), pt.yaw(), pt.pitch()); Location loc = new Location(world, pt.x(), pt.y(), pt.z(), pt.yaw(), pt.pitch());
player.teleport(loc); player.teleport(loc);
String coords = String.format("%.0f, %.0f, %.0f", pt.x(), pt.y(), pt.z()); String coords = String.format("%.0f, %.0f, %.0f", pt.x(), pt.y(), pt.z());
player.sendMessage(plugin.lang().format("join.teleport-success", "{coords}", coords)); plugin.lang().send(player, "join.teleport-success", "{coords}", coords);
}); });
}); });
}, 40L); }, 40L);
@@ -78,7 +81,9 @@ public class PlayerJoinListener implements Listener {
player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().get("general.separator"));
player.sendMessage(plugin.lang().get("join.pending-header")); player.sendMessage(plugin.lang().get("join.pending-header"));
for (String msg : pending) { for (String msg : pending) {
player.sendMessage(plugin.lang().color(msg)); // Pending-Nachrichten können MiniMessage-Tags enthalten
// → sendRaw() statt color()
plugin.lang().sendRaw(player, msg);
} }
player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().get("general.separator"));
}); });
@@ -94,7 +99,6 @@ public class PlayerJoinListener implements Listener {
// ── Spieler: über geschlossene Tickets informieren ──────────────── // ── Spieler: über geschlossene Tickets informieren ────────────────
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Nur Tickets dieses Spielers laden (nicht ALLE closed Tickets)
List<Ticket> closed = plugin.getDatabaseManager() List<Ticket> closed = plugin.getDatabaseManager()
.getUnnotifiedClosedTicketsByPlayer(player.getUniqueId()); .getUnnotifiedClosedTicketsByPlayer(player.getUniqueId());

View File

@@ -293,7 +293,7 @@ public class LanguageManager {
} }
/** /**
* Sendet eine Nachricht an einen CommandSender. * Sendet eine Nachricht an einen CommandSender (aus einem lang-Key).
* *
* Enthält der Rohtext MiniMessage-Tags mit interaktiven Elementen * Enthält der Rohtext MiniMessage-Tags mit interaktiven Elementen
* (click, hover, insertion), wird die Nachricht als Adventure-Component * (click, hover, insertion), wird die Nachricht als Adventure-Component
@@ -312,6 +312,38 @@ public class LanguageManager {
for (int i = 0; i + 1 < replacements.length; i += 2) for (int i = 0; i + 1 < replacements.length; i += 2)
raw = raw.replace(replacements[i], replacements[i + 1]); raw = raw.replace(replacements[i], replacements[i + 1]);
sendProcessed(sender, raw, needsPrefix(key));
}
/**
* Sendet einen bereits zusammengebauten Roh-String (KEIN lang-Key) durch
* die vollständige MiniMessage-Pipeline inkl. Legacy-Konvertierung.
*
* Verwende diese Methode wenn du Strings programmatisch zusammenbaust
* (z.B. Rating-Prompt aus mehreren Keys) und trotzdem klickbare
* run_command-Tags benötigst.
*
* Prefix wird NICHT automatisch vorangestellt.
*/
public void sendRaw(CommandSender sender, String rawText) {
if (rawText == null || rawText.isEmpty()) return;
sendProcessed(sender, rawText, false);
}
/**
* Sendet einen bereits zusammengebauten Roh-String mit optionalem Prefix
* durch die vollständige MiniMessage-Pipeline.
*/
public void sendRaw(CommandSender sender, String rawText, boolean withPrefix) {
if (rawText == null || rawText.isEmpty()) return;
sendProcessed(sender, rawText, withPrefix);
}
/**
* Interne Kernmethode: Verarbeitet einen vorbereiteten String und sendet ihn.
* Entscheidet automatisch zwischen MiniMessage-Component und Legacy-String.
*/
private void sendProcessed(CommandSender sender, String raw, boolean addPrefix) {
if (hasMiniMessageInteractiveTags(raw)) { if (hasMiniMessageInteractiveTags(raw)) {
// MiniMessage-Pfad: interaktive Komponenten (click/hover) // MiniMessage-Pfad: interaktive Komponenten (click/hover)
// Legacy-&-Farbcodes in MiniMessage-Äquivalente umwandeln damit // Legacy-&-Farbcodes in MiniMessage-Äquivalente umwandeln damit
@@ -320,7 +352,7 @@ public class LanguageManager {
Component component = MINI.deserialize(mm); Component component = MINI.deserialize(mm);
// Prefix als Component voranstellen wenn nötig // Prefix als Component voranstellen wenn nötig
if (needsPrefix(key)) { if (addPrefix) {
Component prefixComp = MINI.deserialize(legacyToMiniMessage( Component prefixComp = MINI.deserialize(legacyToMiniMessage(
color("&8[&6Ticket&8] &r"))); color("&8[&6Ticket&8] &r")));
component = prefixComp.append(component); component = prefixComp.append(component);
@@ -332,7 +364,7 @@ public class LanguageManager {
} else { } else {
// Legacy-Pfad: §-Codes, kein Adventure nötig // Legacy-Pfad: §-Codes, kein Adventure nötig
String text = color(raw); String text = color(raw);
sender.sendMessage(needsPrefix(key) ? prefix + text : text); sender.sendMessage(addPrefix ? prefix + text : text);
} }
} }
@@ -454,37 +486,6 @@ public class LanguageManager {
return sb.toString(); return sb.toString();
} }
/**
* Sendet eine Adventure-Component an einen CommandSender.
*
* Der Cast auf net.kyori.adventure.audience.Audience umgeht das compile-time
* Problem, dass Spigots CommandSender-Interface sendMessage(Component) nicht
* direkt exponiert zur Laufzeit implementiert jeder CommandSender auf
* Paper/Spigot 1.18+ das Audience-Interface.
*/
private void sendComponent(CommandSender sender, Component component) {
if (sender instanceof net.kyori.adventure.audience.Audience audience) {
try {
audience.sendMessage(component);
} catch (NoSuchMethodError | AbstractMethodError e) {
// Fallback für reines Spigot ohne Adventure-Patch:
// Component in Legacy-String serialisieren (click/hover gehen verloren,
// aber der Text bleibt lesbar)
String legacy = LegacyComponentSerializer.legacySection().serialize(component);
sender.sendMessage(legacy);
if (plugin.isDebug()) {
plugin.getLogger().info("[LanguageManager] Adventure-API nicht verfügbar "
+ "interaktive Tags werden als Plain-Text gesendet. "
+ "Wechsel auf Paper für volle MiniMessage-Unterstützung.");
}
}
} else {
// Kein Audience (z. B. alte Konsole) → Legacy-Fallback
String legacy = LegacyComponentSerializer.legacySection().serialize(component);
sender.sendMessage(legacy);
}
}
// ── Intern ─────────────────────────────────────────────────────────────── // ── Intern ───────────────────────────────────────────────────────────────
private boolean needsPrefix(String key) { private boolean needsPrefix(String key) {

View File

@@ -45,6 +45,9 @@ public class TicketManager {
/** /**
* Benachrichtigt alle Supporter/Admins über ein neues Ticket. * Benachrichtigt alle Supporter/Admins über ein neues Ticket.
* Bei BungeeCord wird die Nachricht an alle Server weitergeleitet. * Bei BungeeCord wird die Nachricht an alle Server weitergeleitet.
*
* Die gui-hint-Nachricht wird über lang().send() geroutet, damit
* ein <click:run_command:...>-Tag in der lang.yml funktioniert.
*/ */
public void notifyTeam(Ticket ticket) { public void notifyTeam(Ticket ticket) {
String creatorName = ticket.getCreatorName() != null ? ticket.getCreatorName() : "Unbekannt"; String creatorName = ticket.getCreatorName() != null ? ticket.getCreatorName() : "Unbekannt";
@@ -66,21 +69,24 @@ public class TicketManager {
serverInfo = plugin.lang().format("notify.team-server", "{server}", ticket.getServerName()); serverInfo = plugin.lang().format("notify.team-server", "{server}", ticket.getServerName());
} }
// Haupt-Benachrichtigung (plain Legacy-String, kein MiniMessage nötig)
String msg = plugin.lang().format("ticket.new-notify", String msg = plugin.lang().format("ticket.new-notify",
"{player}", creatorName, "{player}", creatorName,
"{message}", message, "{message}", message,
"{id}", String.valueOf(ticket.getId())) "{id}", String.valueOf(ticket.getId()))
+ categoryInfo + priorityInfo + serverInfo; + categoryInfo + priorityInfo + serverInfo;
String guiHint = plugin.lang().get("notify.gui-hint");
if (plugin.isBungeeCordEnabled()) { if (plugin.isBungeeCordEnabled()) {
plugin.getBungeeMessenger().broadcastTeamNotification(msg + "\n" + guiHint); // BungeeCord: Roh-String broadcasten (gui-hint separat per sendRaw)
plugin.getBungeeMessenger().broadcastTeamNotification(msg);
plugin.getBungeeMessenger().broadcastTeamNotification(
plugin.lang().get("notify.gui-hint"));
} else { } else {
for (Player p : Bukkit.getOnlinePlayers()) { for (Player p : Bukkit.getOnlinePlayers()) {
if (p.hasPermission("ticket.support") || p.hasPermission("ticket.admin")) { if (p.hasPermission("ticket.support") || p.hasPermission("ticket.admin")) {
p.sendMessage(msg); p.sendMessage(msg);
p.sendMessage(guiHint); // gui-hint über send() routen → MiniMessage greift wenn Tags vorhanden
plugin.lang().send(p, "notify.gui-hint");
} }
} }
} }
@@ -120,14 +126,13 @@ public class TicketManager {
Bukkit.getScheduler().runTask(plugin, () -> { Bukkit.getScheduler().runTask(plugin, () -> {
if (!player.isOnline()) return; if (!player.isOnline()) return;
if (t.getStatus() == TicketStatus.CLAIMED) { if (t.getStatus() == TicketStatus.CLAIMED) {
player.sendMessage(plugin.lang().format("ticket.claimed-notify", plugin.lang().send(player, "ticket.claimed-notify",
"{id}", String.valueOf(t.getId()), "{claimer}", name)); "{id}", String.valueOf(t.getId()), "{claimer}", name);
} else { } else {
String forwardedTo = t.getForwardedToName() != null ? t.getForwardedToName() : "einen Supporter"; String forwardedTo = t.getForwardedToName() != null ? t.getForwardedToName() : "einen Supporter";
player.sendMessage(plugin.lang().format("ticket.forwarded-creator", plugin.lang().send(player, "ticket.forwarded-creator",
"{id}", String.valueOf(t.getId()), "{supporter}", forwardedTo)); "{id}", String.valueOf(t.getId()), "{supporter}", forwardedTo);
} }
// Flag NACH der Nachricht setzen sicher im Hauptthread
Bukkit.getScheduler().runTaskAsynchronously(plugin, Bukkit.getScheduler().runTaskAsynchronously(plugin,
() -> plugin.getDatabaseManager().markClaimerNotified(t.getId())); () -> plugin.getDatabaseManager().markClaimerNotified(t.getId()));
}); });
@@ -163,6 +168,14 @@ public class TicketManager {
notifyCreatorClosed(ticket, null); notifyCreatorClosed(ticket, null);
} }
/**
* Benachrichtigt den Ersteller über die Schließung seines Tickets.
*
* Der Rating-Prompt wird über lang().sendRaw() gesendet, damit
* <click:run_command:...>-Tags in der lang.yml tatsächlich funktionieren.
* Dazu werden die einzelnen Keys zusammengebaut und als ein String
* durch die MiniMessage-Pipeline geschickt.
*/
public void notifyCreatorClosed(Ticket ticket, String closerName) { public void notifyCreatorClosed(Ticket ticket, String closerName) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
plugin.getDatabaseManager().markCloseNotified(ticket.getId())); plugin.getDatabaseManager().markCloseNotified(ticket.getId()));
@@ -170,36 +183,50 @@ public class TicketManager {
String comment = (ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty()) String comment = (ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty())
? ticket.getCloseComment() : ""; ? ticket.getCloseComment() : "";
String msg = plugin.lang().format("ticket.closed-notify", "{id}", String.valueOf(ticket.getId())); // Schließ-Nachricht (normaler send()-Pfad reicht)
String closedKey = "ticket.closed-notify";
// Bewertungsaufforderung aufbauen // Rating-Prompt: Keys einzeln holen und zu einem String zusammenbauen,
String ratingMsg = null; // damit Platzhalter korrekt ersetzt werden, BEVOR sendRaw() aufgerufen wird.
String ratingRaw = null;
if (plugin.getConfig().getBoolean("rating-enabled", true)) { if (plugin.getConfig().getBoolean("rating-enabled", true)) {
String id = String.valueOf(ticket.getId()); String id = String.valueOf(ticket.getId());
ratingMsg = plugin.lang().get("rating.prompt-header") + "\n" ratingRaw = plugin.lang().getRaw("rating.prompt-header") + "\n"
+ plugin.lang().get("rating.prompt-title") + "\n" + plugin.lang().getRaw("rating.prompt-title") + "\n"
+ plugin.lang().format("rating.prompt-good", "{id}", id) + "\n" + plugin.lang().getRaw("rating.prompt-good").replace("{id}", id) + "\n"
+ plugin.lang().format("rating.prompt-bad", "{id}", id) + "\n" + plugin.lang().getRaw("rating.prompt-bad").replace("{id}", id) + "\n"
+ plugin.lang().get("rating.prompt-footer"); + plugin.lang().getRaw("rating.prompt-footer");
// {cmd_rate}-Platzhalter ebenfalls ersetzen
ratingRaw = ratingRaw.replace("{cmd_rate}", plugin.lang().getCmdName("rate"));
} }
final String finalRatingRaw = ratingRaw;
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID()); Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
if (creator != null && creator.isOnline()) { if (creator != null && creator.isOnline()) {
creator.sendMessage(msg); // Schließ-Nachricht
plugin.lang().send(creator, closedKey, "{id}", String.valueOf(ticket.getId()));
// Kommentar des Supports
if (!comment.isEmpty()) if (!comment.isEmpty())
creator.sendMessage(plugin.lang().format("ticket.close-comment-label", "{comment}", comment)); creator.sendMessage(plugin.lang().format("ticket.close-comment-label", "{comment}", comment));
if (ratingMsg != null) // Rating-Prompt: über sendRaw() damit MiniMessage-Tags greifen
creator.sendMessage(ratingMsg); if (finalRatingRaw != null)
plugin.lang().sendRaw(creator, finalRatingRaw);
} else if (plugin.isBungeeCordEnabled()) { } else if (plugin.isBungeeCordEnabled()) {
plugin.getBungeeMessenger().sendMessageToPlayer(ticket.getCreatorUUID(), ticket.getCreatorName(), msg); String closedMsg = plugin.lang().format(closedKey, "{id}", String.valueOf(ticket.getId()));
plugin.getBungeeMessenger().sendMessageToPlayer(ticket.getCreatorUUID(), ticket.getCreatorName(), closedMsg);
if (!comment.isEmpty()) if (!comment.isEmpty())
plugin.getBungeeMessenger().sendMessageToPlayer( plugin.getBungeeMessenger().sendMessageToPlayer(
ticket.getCreatorUUID(), ticket.getCreatorName(), ticket.getCreatorUUID(), ticket.getCreatorName(),
plugin.lang().format("ticket.close-comment-label", "{comment}", comment)); plugin.lang().format("ticket.close-comment-label", "{comment}", comment));
if (ratingMsg != null) // Im BungeeCord-Modus: Rating-Prompt als plain Text (click-Events
// funktionieren nur lokal über Adventure BungeeCord-Messaging
// überträgt keine Component-Events, nur Text)
if (finalRatingRaw != null)
plugin.getBungeeMessenger().sendMessageToPlayer( plugin.getBungeeMessenger().sendMessageToPlayer(
ticket.getCreatorUUID(), ticket.getCreatorName(), ratingMsg); ticket.getCreatorUUID(), ticket.getCreatorName(),
plugin.lang().color(finalRatingRaw));
} else { } else {
savePendingClosedNotification(ticket, comment); savePendingClosedNotification(ticket, comment);
@@ -230,42 +257,34 @@ public class TicketManager {
/** /**
* Sendet die Hilfe-Nachricht an den Spieler. * Sendet die Hilfe-Nachricht an den Spieler.
* * Jede Zeile wird über lang().send() geroutet damit
* Die Befehlsnamen in den lang.yml-Schlüsseln (z.B. help.create) enthalten * eventuelle MiniMessage-Tags funktionieren.
* {cmd_X}-Platzhalter. Der LanguageManager ersetzt diese automatisch
* anhand von language in config.yml:
*
* language: de → /ticket erstellen
* language: en → /ticket create
* language: both → /ticket create (erstellen)
*
* Hier muss kein manueller Sprachcode gelesen werden.
*/ */
public void sendHelpMessage(Player player) { public void sendHelpMessage(Player player) {
player.sendMessage(plugin.lang().get("general.separator")); plugin.lang().send(player, "general.separator");
player.sendMessage(plugin.lang().get("help.header")); plugin.lang().send(player, "help.header");
player.sendMessage(plugin.lang().get("general.separator")); plugin.lang().send(player, "general.separator");
player.sendMessage(plugin.lang().get("help.create")); plugin.lang().send(player, "help.create");
player.sendMessage(plugin.lang().get("help.list")); plugin.lang().send(player, "help.list");
player.sendMessage(plugin.lang().get("help.comment")); plugin.lang().send(player, "help.comment");
if (plugin.getConfig().getBoolean("rating-enabled", true)) if (plugin.getConfig().getBoolean("rating-enabled", true))
player.sendMessage(plugin.lang().get("help.rate")); plugin.lang().send(player, "help.rate");
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) { if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) {
player.sendMessage(plugin.lang().get("help.claim")); plugin.lang().send(player, "help.claim");
player.sendMessage(plugin.lang().get("help.close")); plugin.lang().send(player, "help.close");
} }
if (player.hasPermission("ticket.admin")) { if (player.hasPermission("ticket.admin")) {
player.sendMessage(plugin.lang().get("help.forward")); plugin.lang().send(player, "help.forward");
player.sendMessage(plugin.lang().get("help.blacklist")); plugin.lang().send(player, "help.blacklist");
player.sendMessage(plugin.lang().get("help.reload")); plugin.lang().send(player, "help.reload");
player.sendMessage(plugin.lang().get("help.stats")); plugin.lang().send(player, "help.stats");
} }
player.sendMessage(plugin.lang().get("general.separator")); plugin.lang().send(player, "general.separator");
if (player.hasPermission("ticket.admin") && plugin.isBungeeCordEnabled()) if (player.hasPermission("ticket.admin") && plugin.isBungeeCordEnabled())
player.sendMessage(plugin.lang().format("help.bungee-status", "{server}", plugin.getServerName())); plugin.lang().send(player, "help.bungee-status", "{server}", plugin.getServerName());
} }
// ── Interne Hilfsmethoden ───────────────────────────────────────────── // ── Interne Hilfsmethoden ─────────────────────────────────────────────

View File

@@ -80,7 +80,7 @@ ticket:
# BENACHRICHTIGUNGEN (Team / GUI-Hinweis) # BENACHRICHTIGUNGEN (Team / GUI-Hinweis)
# ============================================================ # ============================================================
notify: notify:
gui-hint: "&7» Klicke &e{cmd_list} &7um die Übersicht zu öffnen." gui-hint: "&7» <click:run_command:/ticket liste><yellow>Klicke hier</yellow></click> &7um die Übersicht zu öffnen."
team-category: " §7[§r{category}§7]" team-category: " §7[§r{category}§7]"
team-priority: " §7Priorität: §r{priority}" team-priority: " §7Priorität: §r{priority}"
team-server: " §7Server: §b{server}" team-server: " §7Server: §b{server}"
@@ -139,8 +139,8 @@ rating:
invalid: "&cUngültige Bewertung! Benutze &egood &coder &ebad&c." invalid: "&cUngültige Bewertung! Benutze &egood &coder &ebad&c."
prompt-header: "&#555555&m " prompt-header: "&#555555&m "
prompt-title: "&6Wie zufrieden bist du mit dem Support?" prompt-title: "&6Wie zufrieden bist du mit dem Support?"
prompt-good: "&a{cmd_rate} {id} good &7 👍 Gut" prompt-good: "<green><click:run_command:/ticket bewerten {id} good>&a👍 Gut bewerten</click></green>"
prompt-bad: "&c{cmd_rate} {id} bad &7 👎 Schlecht" prompt-bad: "<red><click:run_command:/ticket bewerten {id} bad>&c👎 Schlecht bewerten</click></red>"
prompt-footer: "&#555555&m " prompt-footer: "&#555555&m "
# ============================================================ # ============================================================
@@ -538,7 +538,7 @@ gui:
# ============================================================ # ============================================================
join: join:
open-tickets: "&eEs gibt noch &6{count} &eoffene Ticket(s)!" open-tickets: "&eEs gibt noch &6{count} &eoffene Ticket(s)!"
open-tickets-hint: "&7» Tippe &e{cmd_list} &7für die Übersicht." open-tickets-hint: "&7» <click:run_command:/ticket liste><yellow>Klicke hier</yellow></click> &7für die Ticket-Übersicht."
teleport-world-missing: "&cTeleport-Zielwelt &e{world} &cnicht gefunden!" teleport-world-missing: "&cTeleport-Zielwelt &e{world} &cnicht gefunden!"
teleport-success: "&7Du wurdest zur Ticket-Position teleportiert. &8({coords})" teleport-success: "&7Du wurdest zur Ticket-Position teleportiert. &8({coords})"
pending-header: "&6Ticket-Benachrichtigungen &7(während du offline warst):" pending-header: "&6Ticket-Benachrichtigungen &7(während du offline warst):"

View File

@@ -80,7 +80,7 @@ ticket:
# NOTIFICATIONS (Team / GUI hint) # NOTIFICATIONS (Team / GUI hint)
# ============================================================ # ============================================================
notify: notify:
gui-hint: "&7» Click &e{cmd_list} &7to open the overview." gui-hint: "&7» <click:run_command:/ticket list><yellow>Click here</yellow></click> &7to open the overview."
team-category: " §7[§r{category}§7]" team-category: " §7[§r{category}§7]"
team-priority: " §7Priority: §r{priority}" team-priority: " §7Priority: §r{priority}"
team-server: " §7Server: §b{server}" team-server: " §7Server: §b{server}"
@@ -139,8 +139,8 @@ rating:
invalid: "&cInvalid rating! Use &egood &cor &ebad&c." invalid: "&cInvalid rating! Use &egood &cor &ebad&c."
prompt-header: "&#777777&m " prompt-header: "&#777777&m "
prompt-title: "&6How satisfied are you with the support?" prompt-title: "&6How satisfied are you with the support?"
prompt-good: "&a{cmd_rate} {id} good &7 👍 Good" prompt-good: "<green><click:run_command:/ticket rate {id} good>&a👍 Rate Good</click></green>"
prompt-bad: "&c{cmd_rate} {id} bad &7 👎 Bad" prompt-bad: "<red><click:run_command:/ticket rate {id} bad>&c👎 Rate Bad</click></red>"
prompt-footer: "&#777777&m " prompt-footer: "&#777777&m "
# ============================================================ # ============================================================
@@ -538,7 +538,7 @@ gui:
# ============================================================ # ============================================================
join: join:
open-tickets: "&eThere are still &6{count} &eopen ticket(s)!" open-tickets: "&eThere are still &6{count} &eopen ticket(s)!"
open-tickets-hint: "&7» Type &e{cmd_list} &7for the overview." open-tickets-hint: "&7» <click:run_command:/ticket list><yellow>Click here</yellow></click> &7for the ticket overview."
teleport-world-missing: "&cTeleport target world &e{world} &cnot found!" teleport-world-missing: "&cTeleport target world &e{world} &cnot found!"
teleport-success: "&7You have been teleported to the ticket location. &8({coords})" teleport-success: "&7You have been teleported to the ticket location. &8({coords})"
pending-header: "&6Ticket notifications &7(while you were offline):" pending-header: "&6Ticket notifications &7(while you were offline):"

View File

@@ -1,5 +1,5 @@
name: TicketSystem name: TicketSystem
version: 1.1.5 version: 1.1.6
main: de.ticketsystem.TicketPlugin main: de.ticketsystem.TicketPlugin
api-version: 1.20 api-version: 1.20
author: M_Viper author: M_Viper