package de.ticketsystem.commands; import de.ticketsystem.TicketPlugin; import de.ticketsystem.model.ConfigCategory; import de.ticketsystem.model.FaqEntry; import de.ticketsystem.model.Ticket; import de.ticketsystem.model.TicketComment; import de.ticketsystem.model.TicketPriority; import de.ticketsystem.manager.CategoryManager; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class TicketCommand implements CommandExecutor, TabCompleter { private final TicketPlugin plugin; public TicketCommand(TicketPlugin plugin) { this.plugin = plugin; } // ── Subkommando-Auflösung ───────────────────────────────────────────── /** * Normalisiert ein Subkommando auf den internen englischen Schlüssel. * Deutsche UND englische Varianten werden immer akzeptiert – unabhängig * von der language-Einstellung. So kann ein Admin auf einem EN-Server * trotzdem den deutschen Begriff tippen ohne einen Fehler zu bekommen. */ private String normalize(String input) { return switch (input.toLowerCase()) { // Deutsch → intern case "erstellen" -> "create"; case "liste" -> "list"; case "übernehmen", "uebernehmen", "beanspruchen" -> "claim"; case "schließen", "schliessen" -> "close"; case "weiterleiten" -> "forward"; case "neuladen" -> "reload"; case "migrieren" -> "migrate"; case "exportieren" -> "export"; case "importieren" -> "import"; case "statistik" -> "stats"; case "archivieren" -> "archive"; case "kommentar" -> "comment"; case "sperrliste" -> "blacklist"; case "bewerten" -> "rate"; case "priorität", "prioritaet" -> "setpriority"; case "hilfe" -> "help"; // Englisch + alles andere → unverändert default -> input.toLowerCase(); }; } // ── onCommand ───────────────────────────────────────────────────────── @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (!(sender instanceof Player player)) { sender.sendMessage(plugin.lang().get("general.console-only")); return true; } if (args.length == 0) { plugin.getTicketManager().sendHelpMessage(player); return true; } switch (normalize(args[0])) { case "create" -> handleCreate(player, args); case "list" -> handleList(player); case "claim" -> handleClaim(player, args); case "close" -> handleClose(player, args); case "forward" -> handleForward(player, args); case "reload" -> handleReload(player); case "migrate" -> handleMigrate(player, args); case "export" -> handleExport(player, args); case "import" -> handleImport(player, args); case "stats" -> handleStats(player); case "top" -> handleTop(player); case "archive" -> handleArchive(player); case "comment" -> handleComment(player, args); case "blacklist" -> handleBlacklist(player, args); case "rate" -> handleRate(player, args); case "setpriority" -> handleSetPriority(player, args); case "faq" -> handleFaq(player, args); default -> plugin.getTicketManager().sendHelpMessage(player); } return true; } // ── /ticket create ──────────────────────────────────────────────────── private void handleCreate(Player player, String[] args) { if (!player.hasPermission("ticket.create")) { plugin.lang().send(player, "general.no-permission"); return; } if (plugin.getDatabaseManager().isBlacklisted(player.getUniqueId())) { plugin.lang().send(player, "create.blacklist-blocked"); return; } if (args.length < 2) { player.sendMessage(plugin.lang().get("create.usage")); if (plugin.getConfig().getBoolean("categories-enabled", true)) player.sendMessage(plugin.lang().get("create.categories-hint")); if (plugin.getConfig().getBoolean("priorities-enabled", true)) player.sendMessage(plugin.lang().get("create.priorities-hint")); return; } if (plugin.getTicketManager().hasCooldown(player.getUniqueId())) { long rem = plugin.getTicketManager().getRemainingCooldown(player.getUniqueId()); plugin.lang().send(player, "general.cooldown", "{seconds}", String.valueOf(rem)); return; } if (plugin.getTicketManager().hasReachedTicketLimit(player.getUniqueId())) { int max = plugin.getConfig().getInt("max-open-tickets-per-player", 2); plugin.lang().send(player, "create.max-tickets", "{max}", String.valueOf(max)); return; } CategoryManager cm = plugin.getCategoryManager(); ConfigCategory category = cm.getDefault(); TicketPriority priority = TicketPriority.NORMAL; int msgStart = 1; boolean categoriesOn = plugin.getConfig().getBoolean("categories-enabled", true); boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true); if (args.length >= 3) { if (categoriesOn) { ConfigCategory parsedCat = cm.resolve(args[1]); if (parsedCat != null) { category = parsedCat; msgStart = 2; if (prioritiesOn && args.length >= 4) { TicketPriority parsedPrio = parsePriority(args[2]); if (parsedPrio != null) { priority = parsedPrio; msgStart = 3; } } } else if (prioritiesOn) { TicketPriority parsedPrio = parsePriority(args[1]); if (parsedPrio != null) { priority = parsedPrio; msgStart = 2; } } } else if (prioritiesOn) { TicketPriority parsedPrio = parsePriority(args[1]); if (parsedPrio != null) { priority = parsedPrio; msgStart = 2; } } } String message = String.join(" ", Arrays.copyOfRange(args, msgStart, args.length)); int maxLen = plugin.getConfig().getInt("max-description-length", 100); if (message.isEmpty()) { plugin.lang().send(player, "create.no-description"); return; } if (message.length() > maxLen) { plugin.lang().send(player, "create.too-long", "{max}", String.valueOf(maxLen)); return; } final ConfigCategory fCat = category; final TicketPriority fPrio = priority; Ticket ticket = new Ticket(player.getUniqueId(), player.getName(), message, player.getLocation()); ticket.setCategoryKey(fCat.getKey()); ticket.setPriority(fPrio); ticket.setServerName(plugin.getServerName()); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { int id = plugin.getDatabaseManager().createTicket(ticket); if (id == -1) { Bukkit.getScheduler().runTask(plugin, () -> plugin.lang().send(player, "system.db-create-error")); return; } ticket.setId(id); plugin.getTicketCache().put(ticket); plugin.getTicketManager().setCooldown(player.getUniqueId()); Bukkit.getScheduler().runTask(plugin, () -> { String catInfo = categoriesOn ? " §7[" + fCat.getColored() + "§7]" : ""; String prioInfo = prioritiesOn ? " §7[" + fPrio.getColored() + "§7]" : ""; player.sendMessage(plugin.lang().format("ticket.created", "{id}", String.valueOf(id)) + catInfo + prioInfo); plugin.getTicketManager().notifyTeam(ticket); }); }); } // ── /ticket list ────────────────────────────────────────────────────── private void handleList(Player player) { if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> Bukkit.getScheduler().runTask(plugin, () -> plugin.getTicketGUI().openGUI(player))); } else { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> Bukkit.getScheduler().runTask(plugin, () -> plugin.getTicketGUI().openPlayerGUI(player))); } } // ── /ticket claim ───────────────────────────────────────────────────── private void handleClaim(Player player, String[] args) { if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 2) { player.sendMessage(plugin.lang().get("claim.usage")); return; } int id; try { id = Integer.parseInt(args[1]); } catch (NumberFormatException e) { plugin.lang().send(player, "general.invalid-id"); return; } final int ticketId = id; Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { boolean ok = plugin.getDatabaseManager().claimTicket(ticketId, player.getUniqueId(), player.getName()); Bukkit.getScheduler().runTask(plugin, () -> { if (!ok) { plugin.lang().send(player, "general.already-claimed"); return; } Ticket t = getCachedOrFetch(ticketId); if (t == null) return; player.sendMessage(plugin.lang().format("ticket.claimed", "{id}", String.valueOf(ticketId), "{player}", t.getCreatorName())); plugin.getTicketManager().notifyCreatorClaimed(t); plugin.getTicketCache().invalidate(ticketId); }); }); } // ── /ticket close ───────────────────────────────────────────────────── private void handleClose(Player player, String[] args) { if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 2) { player.sendMessage(plugin.lang().get("close.usage")); return; } int id; try { id = Integer.parseInt(args[1]); } catch (NumberFormatException e) { plugin.lang().send(player, "general.invalid-id"); return; } final int ticketId = id; final String comment = args.length > 2 ? String.join(" ", Arrays.copyOfRange(args, 2, args.length)) : ""; Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { boolean ok = plugin.getDatabaseManager().closeTicket(ticketId, comment); if (ok) { Ticket t = getCachedOrFetch(ticketId); if (t != null) plugin.getDatabaseManager().recordClosedTicket(t, player.getName()); plugin.getTicketCache().invalidate(ticketId); Bukkit.getScheduler().runTask(plugin, () -> { player.sendMessage(plugin.lang().format("ticket.closed", "{id}", String.valueOf(ticketId))); if (t != null) { t.setCloseComment(comment); plugin.getTicketManager().notifyCreatorClosed(t, player.getName()); } }); } else { Bukkit.getScheduler().runTask(plugin, () -> plugin.lang().send(player, "general.ticket-not-found")); } }); } // ── /ticket forward ─────────────────────────────────────────────────── private void handleForward(Player player, String[] args) { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 3) { player.sendMessage(plugin.lang().get("forward.usage")); return; } int id; try { id = Integer.parseInt(args[1]); } catch (NumberFormatException e) { plugin.lang().send(player, "general.invalid-id"); return; } Player target = Bukkit.getPlayer(args[2]); if (target == null) { player.sendMessage(plugin.isBungeeCordEnabled() ? plugin.lang().format("forward.bungee-offline", "{player}", args[2]) : plugin.lang().get("forward.local-not-found")); return; } final int ticketId = id; final String fromName = player.getName(); final Player t = target; Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { boolean ok = plugin.getDatabaseManager().forwardTicket(ticketId, t.getUniqueId(), t.getName()); plugin.getTicketCache().invalidate(ticketId); Bukkit.getScheduler().runTask(plugin, () -> { if (!ok) { plugin.lang().send(player, "general.ticket-not-found"); return; } Ticket ticket = getCachedOrFetch(ticketId); if (ticket == null) return; player.sendMessage(plugin.lang().format("ticket.forwarded", "{id}", String.valueOf(ticketId), "{player}", t.getName())); plugin.getTicketManager().notifyForwardedTo(ticket, fromName); plugin.getTicketManager().notifyCreatorForwarded(ticket); }); }); } // ── /ticket comment ─────────────────────────────────────────────────── private void handleComment(Player player, String[] args) { if (!player.hasPermission("ticket.create") && !player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 3) { player.sendMessage(plugin.lang().get("comment.usage")); return; } int id; try { id = Integer.parseInt(args[1]); } catch (NumberFormatException e) { plugin.lang().send(player, "general.invalid-id"); return; } String msg = String.join(" ", Arrays.copyOfRange(args, 2, args.length)); if (msg.length() > 500) { player.sendMessage(plugin.lang().get("comment.too-long")); return; } final int ticketId = id; final String message = msg; Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { Ticket ticket = getCachedOrFetch(ticketId); if (ticket == null) { Bukkit.getScheduler().runTask(plugin, () -> plugin.lang().send(player, "general.ticket-not-found")); return; } boolean isOwner = ticket.getCreatorUUID().equals(player.getUniqueId()); boolean isStaff = player.hasPermission("ticket.support") || player.hasPermission("ticket.admin"); if (!isOwner && !isStaff) { Bukkit.getScheduler().runTask(plugin, () -> plugin.lang().send(player, "comment.no-permission")); return; } TicketComment comment = new TicketComment(ticketId, player.getUniqueId(), player.getName(), message); boolean ok = plugin.getDatabaseManager().addComment(comment); Bukkit.getScheduler().runTask(plugin, () -> { if (ok) { player.sendMessage(plugin.lang().format("comment.saved", "{id}", String.valueOf(ticketId))); notifyCommentReceivers(player, ticket, message); } else { player.sendMessage(plugin.lang().get("comment.error")); } }); }); } private void notifyCommentReceivers(Player author, Ticket ticket, String message) { String onlineMsg = plugin.lang().format("comment.notify-online", "{id}", String.valueOf(ticket.getId()), "{author}", author.getName(), "{message}", message); String offlineMsg = plugin.lang().format("comment.notify-offline", "{id}", String.valueOf(ticket.getId()), "{author}", author.getName(), "{message}", message); if (!ticket.getCreatorUUID().equals(author.getUniqueId())) { Player creator = Bukkit.getPlayer(ticket.getCreatorUUID()); if (creator != null && creator.isOnline()) { creator.sendMessage(onlineMsg); } else if (plugin.isBungeeCordEnabled()) { plugin.getBungeeMessenger().sendMessageToPlayer( ticket.getCreatorUUID(), ticket.getCreatorName(), onlineMsg); } else { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> plugin.getDatabaseManager().addPendingNotification(ticket.getCreatorUUID(), offlineMsg)); } } if (!author.hasPermission("ticket.support") && !author.hasPermission("ticket.admin")) { if (plugin.isBungeeCordEnabled()) { plugin.getBungeeMessenger().broadcastTeamNotification(onlineMsg); } else { var 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 claimerMsg = plugin.lang().format("comment.claimer-offline", "{id}", String.valueOf(ticket.getId()), "{author}", author.getName(), "{message}", message); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> plugin.getDatabaseManager().addPendingNotification(claimerUUID, claimerMsg)); } } for (Player p : Bukkit.getOnlinePlayers()) { if (!p.equals(author) && (p.hasPermission("ticket.support") || p.hasPermission("ticket.admin"))) p.sendMessage(onlineMsg); } } } } // ── /ticket rate ────────────────────────────────────────────────────── private void handleRate(Player player, String[] args) { if (!plugin.getConfig().getBoolean("rating-enabled", true)) { plugin.lang().send(player, "rating.disabled"); return; } if (args.length < 3) { player.sendMessage(plugin.lang().get("rating.usage")); return; } int id; try { id = Integer.parseInt(args[1]); } catch (NumberFormatException e) { plugin.lang().send(player, "general.invalid-id"); return; } String raw = args[2].toLowerCase(); String rating; if (raw.equals("good") || raw.equals("gut") || raw.equals("thumbsup")) rating = "THUMBS_UP"; else if (raw.equals("bad") || raw.equals("schlecht") || raw.equals("thumbsdown")) rating = "THUMBS_DOWN"; else { player.sendMessage(plugin.lang().get("rating.invalid")); return; } final int ticketId = id; final String finalRating = rating; Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { Ticket ticket = getCachedOrFetch(ticketId); if (ticket == null) { Bukkit.getScheduler().runTask(plugin, () -> plugin.lang().send(player, "general.ticket-not-found")); return; } if (!ticket.getCreatorUUID().equals(player.getUniqueId())) { Bukkit.getScheduler().runTask(plugin, () -> plugin.lang().send(player, "rating.not-yours")); return; } if (ticket.hasRating()) { Bukkit.getScheduler().runTask(plugin, () -> plugin.lang().send(player, "rating.already-rated")); return; } boolean ok = plugin.getDatabaseManager().rateTicket(ticketId, finalRating); plugin.getTicketCache().invalidate(ticketId); Bukkit.getScheduler().runTask(plugin, () -> { if (ok) player.sendMessage("THUMBS_UP".equals(finalRating) ? plugin.lang().get("rating.saved-good") : plugin.lang().get("rating.saved-bad")); else player.sendMessage(plugin.lang().get("rating.not-closeable")); }); }); } // ── /ticket blacklist ───────────────────────────────────────────────── private void handleBlacklist(Player player, String[] args) { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 2) { player.sendMessage(plugin.lang().get("blacklist.usage")); return; } switch (args[1].toLowerCase()) { case "add", "hinzufügen", "hinzufuegen" -> { if (args.length < 3) { player.sendMessage(plugin.lang().get("blacklist.usage-add")); return; } String targetName = args[2]; String reason = args.length > 3 ? String.join(" ", Arrays.copyOfRange(args, 3, args.length)) : "Missbrauch"; @SuppressWarnings("deprecation") OfflinePlayer target = Bukkit.getOfflinePlayer(targetName); if (target.getUniqueId() == null) { plugin.lang().send(player, "general.player-not-found"); return; } Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { boolean ok = plugin.getDatabaseManager().addBlacklist( target.getUniqueId(), targetName, reason, player.getName()); Bukkit.getScheduler().runTask(plugin, () -> { if (ok) player.sendMessage(plugin.lang().format("blacklist.added", "{player}", targetName, "{reason}", reason)); else player.sendMessage(plugin.lang().get("blacklist.already")); }); }); } case "remove", "entfernen" -> { if (args.length < 3) { player.sendMessage(plugin.lang().get("blacklist.usage-remove")); return; } @SuppressWarnings("deprecation") OfflinePlayer target = Bukkit.getOfflinePlayer(args[2]); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { boolean ok = plugin.getDatabaseManager().removeBlacklist(target.getUniqueId()); Bukkit.getScheduler().runTask(plugin, () -> { if (ok) player.sendMessage(plugin.lang().format("blacklist.removed", "{player}", args[2])); else player.sendMessage(plugin.lang().get("blacklist.not-found")); }); }); } case "list", "liste" -> { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { var list = plugin.getDatabaseManager().getBlacklist(); Bukkit.getScheduler().runTask(plugin, () -> { player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().format("blacklist.list-header", "{count}", String.valueOf(list.size()))); player.sendMessage(plugin.lang().get("general.separator")); if (list.isEmpty()) { player.sendMessage(plugin.lang().get("blacklist.list-empty")); } else { for (String[] e : list) player.sendMessage(plugin.lang().format("blacklist.list-entry", "{player}", e[1], "{reason}", e[2], "{by}", e[3])); } player.sendMessage(plugin.lang().get("general.separator")); }); }); } default -> player.sendMessage(plugin.lang().get("blacklist.usage")); } } // ── /ticket top ─────────────────────────────────────────────────────── private void handleTop(Player player) { if (!player.hasPermission("ticket.create") && !player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { List top = plugin.getDatabaseManager().getTopCreators(5); Bukkit.getScheduler().runTask(plugin, () -> { player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().get("top.header")); player.sendMessage(plugin.lang().get("general.separator")); if (top.isEmpty()) { player.sendMessage(plugin.lang().get("top.empty")); } else { String[] medals = {"&e🥇", "&7🥈", "&6🥉", "&7#4", "&7#5"}; for (String[] entry : top) { int rankIdx = Integer.parseInt(entry[0]) - 1; String medal = plugin.lang().color(rankIdx < medals.length ? medals[rankIdx] : "&7#" + entry[0]); int cnt = Integer.parseInt(entry[2]); String label = cnt == 1 ? plugin.lang().get("stats.top-ticket-label") : plugin.lang().get("stats.top-tickets-label"); player.sendMessage(plugin.lang().format("top.entry", "{medal}", medal, "{name}", String.format("%-16s", entry[1]), "{count}", entry[2], "{label}", label)); } } player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().get("top.footer")); }); }); } // ── /ticket reload ──────────────────────────────────────────────────── private void handleReload(Player player) { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } // Reihenfolge zwingend: // 1. Config neu laden (liest language neu ein) // 2. lang().reload() (liest language aus der frischen Config, baut cmdNames neu) // 3. GUI reloadConfig() (liest Rows/Slots aus der frischen Config) // 4. weitere Manager (nutzen ggf. frische lang-Texte) plugin.reloadConfig(); plugin.lang().reload(); plugin.reloadSettings(); // serverName & debug-Flag aktualisieren plugin.getTicketGUI().reloadConfig(); // Slots, Rows & Materialien neu laden plugin.getTicketGUI().reloadTitles(); // Inventar-Titel aktualisieren plugin.getFaqGUI().reloadConfig(); // FAQ-Slots, Rows & Materialien neu laden plugin.getCategoryManager().reload(); plugin.getFaqManager().reload(); plugin.getTicketCache().clear(); player.sendMessage(plugin.lang().get("reload.success")); if (plugin.isBungeeCordEnabled()) player.sendMessage(plugin.lang().format("reload.bungee-info", "{server}", plugin.getServerName())); } // ── /ticket archive ─────────────────────────────────────────────────── private void handleArchive(Player player) { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { int count = plugin.getDatabaseManager().archiveClosedTickets(); Bukkit.getScheduler().runTask(plugin, () -> { if (count > 0) player.sendMessage(plugin.lang().format("system.archive-success", "{count}", String.valueOf(count))); else player.sendMessage(plugin.lang().get("system.archive-fail")); }); }); } // ── /ticket stats ───────────────────────────────────────────────────── private void handleStats(Player player) { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { var stats = plugin.getDatabaseManager().getTicketStats(); var staffRatings = plugin.getDatabaseManager().getStaffRatings(); var topCreators = plugin.getDatabaseManager().getTopCreators(5); Bukkit.getScheduler().runTask(plugin, () -> { player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().get("stats.header")); player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().format("stats.total", "{count}", String.valueOf(stats.total))); player.sendMessage(plugin.lang().format("stats.open", "{count}", String.valueOf(stats.open))); player.sendMessage(plugin.lang().format("stats.closed", "{count}", String.valueOf(stats.closed))); player.sendMessage(plugin.lang().format("stats.forwarded", "{count}", String.valueOf(stats.forwarded))); if (plugin.getConfig().getBoolean("rating-enabled", true)) { player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().get("stats.ratings-header")); player.sendMessage(plugin.lang().format("stats.ratings-summary", "{up}", String.valueOf(stats.thumbsUp), "{down}", String.valueOf(stats.thumbsDown))); int totalRated = stats.thumbsUp + stats.thumbsDown; if (totalRated > 0) { int pct = (int) Math.round(stats.thumbsUp * 100.0 / totalRated); player.sendMessage(plugin.lang().format("stats.ratings-percent", "{percent}", String.valueOf(pct))); } if (!staffRatings.isEmpty()) { player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().get("stats.staff-header")); player.sendMessage(plugin.lang().get("stats.staff-table-header")); for (String[] row : staffRatings) player.sendMessage(plugin.lang().format("stats.staff-entry", "{name}", String.format("%-16s", row[0]), "{up}", String.format("%-5s", row[1]), "{down}", String.format("%-5s", row[2]), "{total}", String.format("%-8s", row[3]), "{percent}", row[4])); } } if (plugin.isBungeeCordEnabled() && !stats.byServer.isEmpty()) { player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().get("stats.servers-header")); stats.byServer.entrySet().stream() .sorted((a, b) -> b.getValue() - a.getValue()) .forEach(e -> player.sendMessage(plugin.lang().format("stats.server-entry", "{server}", e.getKey(), "{count}", String.valueOf(e.getValue())))); } player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().get("stats.top-header")); if (topCreators.isEmpty()) { player.sendMessage(plugin.lang().get("stats.top-empty")); } else { String[] medals = {"&e🥇", "&7🥈", "&6🥉", "&7#4", "&7#5"}; for (String[] entry : topCreators) { int ri = Integer.parseInt(entry[0]) - 1; String medal = plugin.lang().color(ri < medals.length ? medals[ri] : "&7#" + entry[0]); int cnt = Integer.parseInt(entry[2]); String label = cnt == 1 ? plugin.lang().get("stats.top-ticket-label") : plugin.lang().get("stats.top-tickets-label"); player.sendMessage(plugin.lang().format("stats.top-entry", "{medal}", medal, "{name}", String.format("%-16s", entry[1]), "{count}", entry[2], "{label}", label)); } } player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().format("stats.cache-info", "{count}", String.valueOf(plugin.getTicketCache().size()))); }); }); } // ── /ticket migrate ─────────────────────────────────────────────────── private void handleMigrate(Player player, String[] args) { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 2) { player.sendMessage(plugin.lang().get("migrate.usage")); return; } Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { String mode = args[1].toLowerCase(); int migrated = 0; if (mode.equals("tomysql")) migrated = plugin.getDatabaseManager().migrateToMySQL(); else if (mode.equals("tofile")) migrated = plugin.getDatabaseManager().migrateToFile(); else { Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.lang().get("system.unknown-mode"))); return; } int f = migrated; Bukkit.getScheduler().runTask(plugin, () -> { if (f > 0) player.sendMessage(plugin.lang().format("system.migration-success", "{count}", String.valueOf(f))); else player.sendMessage(plugin.lang().get("system.migration-fail")); }); }); } // ── /ticket export ──────────────────────────────────────────────────── private void handleExport(Player player, String[] args) { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 2) { player.sendMessage(plugin.lang().get("export.usage")); return; } String filename = args[1]; File exportFile = new File(plugin.getDataFolder(), filename); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { int count = plugin.getDatabaseManager().exportTickets(exportFile); Bukkit.getScheduler().runTask(plugin, () -> { if (count > 0) player.sendMessage(plugin.lang().format("system.export-success", "{count}", String.valueOf(count), "{file}", filename)); else player.sendMessage(plugin.lang().get("system.export-fail")); }); }); } // ── /ticket import ──────────────────────────────────────────────────── private void handleImport(Player player, String[] args) { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 2) { player.sendMessage(plugin.lang().get("import.usage")); return; } String filename = args[1]; File importFile = new File(plugin.getDataFolder(), filename); if (!importFile.exists()) { player.sendMessage(plugin.lang().format("system.file-not-found", "{file}", filename)); return; } Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { int count = plugin.getDatabaseManager().importTickets(importFile); Bukkit.getScheduler().runTask(plugin, () -> { if (count > 0) player.sendMessage(plugin.lang().format("system.import-success", "{count}", String.valueOf(count))); else player.sendMessage(plugin.lang().get("system.import-fail")); }); }); } // ── /ticket setpriority ─────────────────────────────────────────────── private void handleSetPriority(Player player, String[] args) { if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (!plugin.getConfig().getBoolean("priorities-enabled", true)) { player.sendMessage(plugin.lang().get("setpriority.disabled")); return; } if (args.length < 3) { player.sendMessage(plugin.lang().get("setpriority.usage")); return; } int ticketId; try { ticketId = Integer.parseInt(args[1]); } catch (NumberFormatException e) { player.sendMessage(plugin.lang().format("general.invalid-player-id", "{id}", args[1])); return; } TicketPriority prio = parsePriority(args[2]); if (prio == null) { player.sendMessage(plugin.lang().get("setpriority.invalid")); return; } final int finalId = ticketId; final TicketPriority finalPrio = prio; Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { boolean ok = plugin.getDatabaseManager().setTicketPriority(finalId, finalPrio); plugin.getTicketCache().invalidate(finalId); Bukkit.getScheduler().runTask(plugin, () -> { if (ok) player.sendMessage(plugin.lang().format("setpriority.success", "{id}", String.valueOf(finalId), "{priority}", finalPrio.getColored())); else player.sendMessage(plugin.lang().format("setpriority.not-found", "{id}", String.valueOf(finalId))); }); }); } // ── /ticket faq ─────────────────────────────────────────────────────── private void handleFaq(Player player, String[] args) { if (args.length == 1) { plugin.getFaqGUI().openFaqGUI(player); return; } switch (args[1].toLowerCase()) { case "add", "hinzufügen", "hinzufuegen" -> { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 3) { player.sendMessage(plugin.lang().get("faq.usage-add")); player.sendMessage(plugin.lang().get("faq.usage-add-example")); return; } String full = String.join(" ", Arrays.copyOfRange(args, 2, args.length)); String[] parts = full.split("\\s*\\|\\s*", 2); if (parts.length < 2 || parts[0].isBlank() || parts[1].isBlank()) { player.sendMessage(plugin.lang().get("faq.separator-missing")); player.sendMessage(plugin.lang().get("faq.separator-example")); return; } FaqEntry created = plugin.getFaqManager().add(parts[0].trim(), parts[1].trim()); player.sendMessage(plugin.lang().format("faq.created", "{id}", String.valueOf(created.getId()))); player.sendMessage(plugin.lang().format("faq.created-question", "{question}", created.getQuestion())); player.sendMessage(plugin.lang().format("faq.created-answer", "{answer}", created.getAnswer())); } case "edit", "bearbeiten" -> { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 4) { player.sendMessage(plugin.lang().get("faq.usage-edit")); return; } int id; try { id = Integer.parseInt(args[2]); } catch (NumberFormatException e) { player.sendMessage(plugin.lang().format("faq.invalid-id", "{id}", args[2])); return; } String full = String.join(" ", Arrays.copyOfRange(args, 3, args.length)); String[] parts = full.split("\\s*\\|\\s*", 2); if (parts.length < 2 || parts[0].isBlank() || parts[1].isBlank()) { player.sendMessage(plugin.lang().get("faq.separator-short")); return; } boolean ok = plugin.getFaqManager().edit(id, parts[0].trim(), parts[1].trim()); player.sendMessage(ok ? plugin.lang().format("faq.updated", "{id}", String.valueOf(id)) : plugin.lang().format("faq.not-found", "{id}", String.valueOf(id))); } case "delete", "remove", "löschen", "loeschen", "entfernen" -> { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } if (args.length < 3) { player.sendMessage(plugin.lang().get("faq.usage-delete")); return; } int id; try { id = Integer.parseInt(args[2]); } catch (NumberFormatException e) { player.sendMessage(plugin.lang().format("faq.invalid-id", "{id}", args[2])); return; } boolean ok = plugin.getFaqManager().delete(id); player.sendMessage(ok ? plugin.lang().format("faq.deleted", "{id}", String.valueOf(id)) : plugin.lang().format("faq.not-found", "{id}", String.valueOf(id))); } case "reload", "neuladen" -> { if (!player.hasPermission("ticket.admin")) { plugin.lang().send(player, "general.no-permission"); return; } plugin.getFaqManager().reload(); player.sendMessage(plugin.lang().format("faq.reloaded", "{count}", String.valueOf(plugin.getFaqManager().getAll().size()))); } case "list", "liste" -> { List all = plugin.getFaqManager().getAll(); player.sendMessage(plugin.lang().get("general.separator")); player.sendMessage(plugin.lang().format("faq.list-header", "{count}", String.valueOf(all.size()))); player.sendMessage(plugin.lang().get("general.separator")); if (all.isEmpty()) { player.sendMessage(plugin.lang().get("faq.list-empty")); } else { for (FaqEntry e : all) { player.sendMessage(plugin.lang().format("faq.list-entry", "{id}", String.valueOf(e.getId()), "{question}", e.getQuestion())); player.sendMessage(plugin.lang().format("faq.list-answer", "{answer}", e.getAnswer())); } } player.sendMessage(plugin.lang().get("general.separator")); if (player.hasPermission("ticket.admin")) player.sendMessage(plugin.lang().get("faq.list-admin-hint")); } default -> { player.sendMessage(plugin.lang().get("faq.unknown-sub")); player.sendMessage(plugin.lang().get("faq.hint-open")); if (player.hasPermission("ticket.admin")) player.sendMessage(plugin.lang().get("faq.admin-commands")); } } } // ── Hilfsmethoden ───────────────────────────────────────────────────── private Ticket getCachedOrFetch(int ticketId) { Ticket cached = plugin.getTicketCache().get(ticketId); if (cached != null) return cached; Ticket fresh = plugin.getDatabaseManager().getTicketById(ticketId); if (fresh != null) plugin.getTicketCache().put(fresh); return fresh; } private TicketPriority parsePriority(String input) { if (input == null) return null; return switch (input.toLowerCase()) { case "low", "niedrig" -> TicketPriority.LOW; case "normal" -> TicketPriority.NORMAL; case "high", "hoch" -> TicketPriority.HIGH; case "urgent", "dringend" -> TicketPriority.URGENT; default -> null; }; } // ── Tab-Completion ──────────────────────────────────────────────────── @Override public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { List completions = new ArrayList<>(); if (!(sender instanceof Player player)) return completions; // Immer direkt vom LanguageManager lesen – kein Cache, immer aktuell nach reload final boolean useDe = plugin.lang().acceptsGerman(); final boolean useEn = plugin.lang().acceptsEnglish(); if (args.length == 1) { List subs = new ArrayList<>(); if (useEn) subs.addAll(List.of("create", "list", "comment", "top", "faq")); if (useDe) subs.addAll(List.of("erstellen", "liste", "kommentar", "top", "faq")); if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) { if (useEn) subs.addAll(List.of("claim", "close")); if (useDe) subs.addAll(List.of("übernehmen", "schließen")); } if (plugin.getConfig().getBoolean("rating-enabled", true)) { if (useEn) subs.add("rate"); if (useDe) subs.add("bewerten"); } if (player.hasPermission("ticket.admin")) { if (useEn) subs.addAll(List.of("forward", "reload", "stats", "archive", "migrate", "export", "import", "blacklist")); if (useDe) subs.addAll(List.of("weiterleiten", "neuladen", "statistik", "archivieren", "migrieren", "exportieren", "importieren", "sperrliste")); } if ((player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) && plugin.getConfig().getBoolean("priorities-enabled", true)) { if (useEn) subs.add("setpriority"); if (useDe) subs.add("priorität"); } for (String s : subs) if (s.startsWith(args[0].toLowerCase())) completions.add(s); } else if (args.length == 2 && normalize(args[0]).equals("faq")) { List faqSubs = new ArrayList<>(); if (useEn) faqSubs.add("list"); if (useDe) faqSubs.add("liste"); if (player.hasPermission("ticket.admin")) { if (useEn) faqSubs.addAll(List.of("add", "edit", "delete", "reload")); if (useDe) faqSubs.addAll(List.of("hinzufügen", "bearbeiten", "löschen", "neuladen")); } for (String s : faqSubs) if (s.startsWith(args[1].toLowerCase())) completions.add(s); } else if (args.length == 3 && normalize(args[0]).equals("faq") && (args[1].equalsIgnoreCase("edit") || args[1].equalsIgnoreCase("delete") || args[1].equalsIgnoreCase("bearbeiten") || args[1].equalsIgnoreCase("löschen")) && player.hasPermission("ticket.admin")) { for (FaqEntry e : plugin.getFaqManager().getAll()) completions.add(String.valueOf(e.getId())); } else if (args.length == 2 && normalize(args[0]).equals("create") && plugin.getConfig().getBoolean("categories-enabled", true)) { for (ConfigCategory c : plugin.getCategoryManager().getAll()) if (c.getKey().startsWith(args[1].toLowerCase())) completions.add(c.getKey()); 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 && normalize(args[0]).equals("create") && plugin.getConfig().getBoolean("priorities-enabled", true)) { for (String p : List.of("low", "normal", "high", "urgent")) if (p.startsWith(args[2].toLowerCase())) completions.add(p); } else if (args.length == 3 && normalize(args[0]).equals("setpriority")) { for (String p : List.of("low", "normal", "high", "urgent")) if (p.startsWith(args[2].toLowerCase())) completions.add(p); } else if (args.length == 3 && normalize(args[0]).equals("forward")) { for (Player p : Bukkit.getOnlinePlayers()) if (p.getName().toLowerCase().startsWith(args[2].toLowerCase())) completions.add(p.getName()); } else if (args.length == 2 && normalize(args[0]).equals("blacklist")) { if (useEn) completions.addAll(List.of("add", "remove", "list")); if (useDe) completions.addAll(List.of("hinzufügen", "entfernen", "liste")); } else if (args.length == 3 && normalize(args[0]).equals("blacklist") && (args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("remove") || args[1].equalsIgnoreCase("hinzufügen") || args[1].equalsIgnoreCase("entfernen"))) { for (Player p : Bukkit.getOnlinePlayers()) if (p.getName().toLowerCase().startsWith(args[2].toLowerCase())) completions.add(p.getName()); } else if (args.length == 3 && normalize(args[0]).equals("rate")) { if (useEn) completions.addAll(List.of("good", "bad")); if (useDe) completions.addAll(List.of("gut", "schlecht")); } return completions; } }