Upload folder via GUI - src
This commit is contained in:
@@ -115,11 +115,18 @@ public class TicketPlugin extends JavaPlugin {
|
|||||||
// Manager, GUI, FAQ & Discord-Webhook initialisieren
|
// Manager, GUI, FAQ & Discord-Webhook initialisieren
|
||||||
categoryManager = new CategoryManager(this);
|
categoryManager = new CategoryManager(this);
|
||||||
ticketManager = new TicketManager(this);
|
ticketManager = new TicketManager(this);
|
||||||
faqManager = new FaqManager(this);
|
|
||||||
ticketGUI = new TicketGUI(this);
|
ticketGUI = new TicketGUI(this);
|
||||||
faqGUI = new FaqGUI(this);
|
|
||||||
discordWebhook = new DiscordWebhook(this);
|
discordWebhook = new DiscordWebhook(this);
|
||||||
|
|
||||||
|
// ── FAQ-System (nur initialisieren wenn aktiviert) ─────────────────
|
||||||
|
if (isFaqEnabled()) {
|
||||||
|
faqManager = new FaqManager(this);
|
||||||
|
faqGUI = new FaqGUI(this);
|
||||||
|
getLogger().info("[FAQ] FAQ-System aktiviert.");
|
||||||
|
} else {
|
||||||
|
getLogger().info("[FAQ] FAQ-System deaktiviert (faq-enabled: false in config.yml).");
|
||||||
|
}
|
||||||
|
|
||||||
if (getConfig().getBoolean("discord.enabled", false)) {
|
if (getConfig().getBoolean("discord.enabled", false)) {
|
||||||
String url = getConfig().getString("discord.webhook-url", "");
|
String url = getConfig().getString("discord.webhook-url", "");
|
||||||
if (url.isEmpty()) {
|
if (url.isEmpty()) {
|
||||||
@@ -134,7 +141,9 @@ public class TicketPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
|
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
|
||||||
getServer().getPluginManager().registerEvents(ticketGUI, this);
|
getServer().getPluginManager().registerEvents(ticketGUI, this);
|
||||||
getServer().getPluginManager().registerEvents(faqGUI, this);
|
if (faqGUI != null) {
|
||||||
|
getServer().getPluginManager().registerEvents(faqGUI, this);
|
||||||
|
}
|
||||||
|
|
||||||
// Automatische Archivierung
|
// Automatische Archivierung
|
||||||
int archiveIntervalH = getConfig().getInt("auto-archive-interval-hours", 24);
|
int archiveIntervalH = getConfig().getInt("auto-archive-interval-hours", 24);
|
||||||
@@ -166,6 +175,9 @@ public class TicketPlugin extends JavaPlugin {
|
|||||||
if (webServer != null) webServer.stop();
|
if (webServer != null) webServer.stop();
|
||||||
if (ticketCache != null) ticketCache.clear();
|
if (ticketCache != null) ticketCache.clear();
|
||||||
if (databaseManager != null) databaseManager.disconnect();
|
if (databaseManager != null) databaseManager.disconnect();
|
||||||
|
// Adventure BukkitAudiences sauber schließen
|
||||||
|
if (languageManager != null && languageManager.getAudiences() != null)
|
||||||
|
languageManager.getAudiences().close();
|
||||||
getLogger().info("TicketSystem wurde deaktiviert.");
|
getLogger().info("TicketSystem wurde deaktiviert.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,6 +334,14 @@ public class TicketPlugin extends JavaPlugin {
|
|||||||
TicketPriority.reloadLocalizedNames(this);
|
TicketPriority.reloadLocalizedNames(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt zurück ob das FAQ-System aktiviert ist.
|
||||||
|
* Konfigurierbar in config.yml → faq-enabled (Standard: true)
|
||||||
|
*/
|
||||||
|
public boolean isFaqEnabled() {
|
||||||
|
return getConfig().getBoolean("faq-enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
public static TicketPlugin getInstance() { return instance; }
|
public static TicketPlugin getInstance() { return instance; }
|
||||||
public LanguageManager getLanguageManager() { return languageManager; }
|
public LanguageManager getLanguageManager() { return languageManager; }
|
||||||
public DatabaseManager getDatabaseManager() { return databaseManager; }
|
public DatabaseManager getDatabaseManager() { return databaseManager; }
|
||||||
|
|||||||
@@ -9,17 +9,22 @@ import de.ticketsystem.model.TicketComment;
|
|||||||
import de.ticketsystem.model.TicketPriority;
|
import de.ticketsystem.model.TicketPriority;
|
||||||
import de.ticketsystem.manager.CategoryManager;
|
import de.ticketsystem.manager.CategoryManager;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.World;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
import org.bukkit.command.TabCompleter;
|
import org.bukkit.command.TabCompleter;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.sql.Timestamp;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class TicketCommand implements CommandExecutor, TabCompleter {
|
public class TicketCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
@@ -31,15 +36,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
|
|
||||||
// ── Subkommando-Auflösung ─────────────────────────────────────────────
|
// ── 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) {
|
private String normalize(String input) {
|
||||||
return switch (input.toLowerCase()) {
|
return switch (input.toLowerCase()) {
|
||||||
// Deutsch → intern
|
|
||||||
case "erstellen" -> "create";
|
case "erstellen" -> "create";
|
||||||
case "liste" -> "list";
|
case "liste" -> "list";
|
||||||
case "übernehmen", "uebernehmen",
|
case "übernehmen", "uebernehmen",
|
||||||
@@ -59,7 +57,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
case "priorität", "prioritaet" -> "setpriority";
|
case "priorität", "prioritaet" -> "setpriority";
|
||||||
case "hilfe" -> "help";
|
case "hilfe" -> "help";
|
||||||
case "kategorie" -> "category";
|
case "kategorie" -> "category";
|
||||||
// Englisch + alles andere → unverändert
|
|
||||||
default -> input.toLowerCase();
|
default -> input.toLowerCase();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -68,17 +65,36 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
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) {
|
if (args.length == 0) {
|
||||||
plugin.getTicketManager().sendHelpMessage(player);
|
if (sender instanceof Player player) {
|
||||||
|
plugin.getTicketManager().sendHelpMessage(player);
|
||||||
|
} else {
|
||||||
|
sendConsoleHelp(sender);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (normalize(args[0])) {
|
String sub = normalize(args[0]);
|
||||||
|
|
||||||
|
// ── Konsolen-only: create mit Spielername ────────────────────────
|
||||||
|
if (sender instanceof ConsoleCommandSender) {
|
||||||
|
switch (sub) {
|
||||||
|
case "create" -> { handleCreateConsole(sender, args); return true; }
|
||||||
|
case "reload" -> { handleReloadConsole(sender); return true; }
|
||||||
|
case "archive" -> { handleArchiveConsole(sender); return true; }
|
||||||
|
case "stats" -> { handleStatsConsole(sender); return true; }
|
||||||
|
default -> {
|
||||||
|
sender.sendMessage("[TicketSystem] Konsolen-Befehle: create <Spieler> [Kategorie] <Text> | reload | archive | stats");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Spieler-Befehle ──────────────────────────────────────────────
|
||||||
|
Player player = (Player) sender;
|
||||||
|
|
||||||
|
switch (sub) {
|
||||||
case "create" -> handleCreate(player, args);
|
case "create" -> handleCreate(player, args);
|
||||||
case "list" -> handleList(player);
|
case "list" -> handleList(player);
|
||||||
case "claim" -> handleClaim(player, args);
|
case "claim" -> handleClaim(player, args);
|
||||||
@@ -103,6 +119,179 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── /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) {
|
||||||
|
// args[0] = "create", args[1] = Spielername, args[2..] = [Kategorie] [Priorität] Text
|
||||||
|
if (args.length < 3) {
|
||||||
|
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");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetName = args[1];
|
||||||
|
|
||||||
|
// Spieler suchen (online bevorzugt, sonst OfflinePlayer)
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
OfflinePlayer offlineTarget = Bukkit.getOfflinePlayer(targetName);
|
||||||
|
UUID targetUUID = offlineTarget.getUniqueId();
|
||||||
|
String resolvedName = offlineTarget.getName() != null ? offlineTarget.getName() : targetName;
|
||||||
|
|
||||||
|
CategoryManager cm = plugin.getCategoryManager();
|
||||||
|
ConfigCategory category = cm.getDefault();
|
||||||
|
TicketPriority priority = TicketPriority.NORMAL;
|
||||||
|
int msgStart = 2; // Index ab dem die Beschreibung beginnt (relativ zu args[2..])
|
||||||
|
|
||||||
|
boolean categoriesOn = plugin.getConfig().getBoolean("categories-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) {
|
||||||
|
ConfigCategory parsedCat = cm.resolve(args[2]);
|
||||||
|
if (parsedCat != null) {
|
||||||
|
category = parsedCat;
|
||||||
|
msgStart = 3;
|
||||||
|
if (prioritiesOn && args.length >= 5) {
|
||||||
|
TicketPriority parsedPrio = parsePriority(args[3]);
|
||||||
|
if (parsedPrio != null) { priority = parsedPrio; msgStart = 4; }
|
||||||
|
}
|
||||||
|
} else if (prioritiesOn) {
|
||||||
|
TicketPriority parsedPrio = parsePriority(args[2]);
|
||||||
|
if (parsedPrio != null) { priority = parsedPrio; msgStart = 3; }
|
||||||
|
}
|
||||||
|
} else if (args.length >= 4 && prioritiesOn) {
|
||||||
|
TicketPriority parsedPrio = parsePriority(args[2]);
|
||||||
|
if (parsedPrio != null) { priority = parsedPrio; msgStart = 3; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beschreibung zusammensetzen (args[msgStart+1] weil args[1]=Spielername)
|
||||||
|
int absoluteStart = msgStart + 1; // +1 für den Spielernamen in args[1]
|
||||||
|
if (absoluteStart >= args.length) {
|
||||||
|
console.sendMessage("[TicketSystem] Fehler: Keine Beschreibung angegeben.");
|
||||||
|
console.sendMessage("[TicketSystem] Verwendung: /ticket create <Spielername> [Kategorie] [Priorität] <Beschreibung>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = String.join(" ", Arrays.copyOfRange(args, absoluteStart, args.length));
|
||||||
|
int maxLen = plugin.getConfig().getInt("max-description-length", 100);
|
||||||
|
|
||||||
|
if (message.isEmpty()) {
|
||||||
|
console.sendMessage("[TicketSystem] Fehler: Beschreibung darf nicht leer sein.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.length() > maxLen) {
|
||||||
|
console.sendMessage("[TicketSystem] Fehler: Beschreibung zu lang (max. " + maxLen + " Zeichen).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location: online-Spieler → aktuelle Pos; offline → Spawn der Default-Welt
|
||||||
|
Location location;
|
||||||
|
Player onlineTarget = Bukkit.getPlayer(targetUUID);
|
||||||
|
if (onlineTarget != null) {
|
||||||
|
location = onlineTarget.getLocation();
|
||||||
|
} else {
|
||||||
|
World defaultWorld = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0);
|
||||||
|
location = defaultWorld != null
|
||||||
|
? defaultWorld.getSpawnLocation()
|
||||||
|
: new Location(null, 0, 64, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ConfigCategory fCat = category;
|
||||||
|
final TicketPriority fPrio = priority;
|
||||||
|
final UUID fUUID = targetUUID;
|
||||||
|
final String fName = resolvedName;
|
||||||
|
final Location fLocation = location;
|
||||||
|
final String fMessage = message;
|
||||||
|
|
||||||
|
Ticket ticket = new Ticket(fUUID, fName, fMessage, fLocation);
|
||||||
|
ticket.setCategoryKey(fCat.getKey());
|
||||||
|
ticket.setPriority(fPrio);
|
||||||
|
ticket.setServerName(plugin.getServerName());
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
int id = plugin.getDatabaseManager().createTicket(ticket);
|
||||||
|
if (id == -1) {
|
||||||
|
console.sendMessage("[TicketSystem] Fehler: Ticket konnte nicht in der Datenbank gespeichert werden.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ticket.setId(id);
|
||||||
|
plugin.getTicketCache().put(ticket);
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
boolean catOn = plugin.getConfig().getBoolean("categories-enabled", true);
|
||||||
|
boolean prioOn = plugin.getConfig().getBoolean("priorities-enabled", true);
|
||||||
|
String catInfo = catOn ? " [" + fCat.getName() + "]" : "";
|
||||||
|
String prioInfo = prioOn ? " [" + fPrio.getDisplayName() + "]" : "";
|
||||||
|
|
||||||
|
console.sendMessage("[TicketSystem] Ticket #" + id + " für Spieler '" + fName
|
||||||
|
+ "' erstellt." + catInfo + prioInfo);
|
||||||
|
console.sendMessage("[TicketSystem] Nachricht: " + fMessage);
|
||||||
|
|
||||||
|
// Team benachrichtigen (gleich wie bei normalem /ticket create)
|
||||||
|
plugin.getTicketManager().notifyTeam(ticket);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── /ticket reload (Konsole) ──────────────────────────────────────────
|
||||||
|
|
||||||
|
private void handleReloadConsole(CommandSender console) {
|
||||||
|
plugin.reloadConfig();
|
||||||
|
plugin.lang().reload();
|
||||||
|
plugin.reloadSettings();
|
||||||
|
plugin.getTicketGUI().reloadConfig();
|
||||||
|
plugin.getTicketGUI().reloadTitles();
|
||||||
|
plugin.getFaqGUI().reloadConfig();
|
||||||
|
plugin.getCategoryManager().reload();
|
||||||
|
plugin.getFaqManager().reload();
|
||||||
|
plugin.getTicketCache().clear();
|
||||||
|
console.sendMessage("[TicketSystem] Konfiguration erfolgreich neu geladen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── /ticket archive (Konsole) ─────────────────────────────────────────
|
||||||
|
|
||||||
|
private void handleArchiveConsole(CommandSender console) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
int count = plugin.getDatabaseManager().archiveClosedTickets();
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () ->
|
||||||
|
console.sendMessage("[TicketSystem] " + count + " Ticket(s) archiviert."));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── /ticket stats (Konsole) ───────────────────────────────────────────
|
||||||
|
|
||||||
|
private void handleStatsConsole(CommandSender console) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
var stats = plugin.getDatabaseManager().getTicketStats();
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
console.sendMessage("[TicketSystem] === Statistiken ===");
|
||||||
|
console.sendMessage("[TicketSystem] Gesamt: " + stats.total);
|
||||||
|
console.sendMessage("[TicketSystem] Offen: " + stats.open);
|
||||||
|
console.sendMessage("[TicketSystem] Geschlossen: " + stats.closed);
|
||||||
|
console.sendMessage("[TicketSystem] Weitergeleitet: " + stats.forwarded);
|
||||||
|
if (plugin.getConfig().getBoolean("rating-enabled", true)) {
|
||||||
|
console.sendMessage("[TicketSystem] Bewertungen: 👍 " + stats.thumbsUp + " 👎 " + stats.thumbsDown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Konsolen-Hilfe ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void sendConsoleHelp(CommandSender console) {
|
||||||
|
console.sendMessage("[TicketSystem] Verfügbare Konsolen-Befehle:");
|
||||||
|
console.sendMessage("[TicketSystem] /ticket create <Spieler> [Kategorie] [Priorität] <Text>");
|
||||||
|
console.sendMessage("[TicketSystem] /ticket reload");
|
||||||
|
console.sendMessage("[TicketSystem] /ticket archive");
|
||||||
|
console.sendMessage("[TicketSystem] /ticket stats");
|
||||||
|
}
|
||||||
|
|
||||||
// ── /ticket create ────────────────────────────────────────────────────
|
// ── /ticket create ────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void handleCreate(Player player, String[] args) {
|
private void handleCreate(Player player, String[] args) {
|
||||||
@@ -136,8 +325,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
TicketPriority priority = TicketPriority.NORMAL;
|
TicketPriority priority = TicketPriority.NORMAL;
|
||||||
int msgStart = 1;
|
int msgStart = 1;
|
||||||
|
|
||||||
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);
|
||||||
boolean allowPlayersPrio = plugin.getConfig().getBoolean("allow-players-to-set-priority", false);
|
boolean allowPlayersPrio = plugin.getConfig().getBoolean("allow-players-to-set-priority", false);
|
||||||
boolean isTeam = player.hasPermission("ticket.support") || player.hasPermission("ticket.admin");
|
boolean isTeam = player.hasPermission("ticket.support") || player.hasPermission("ticket.admin");
|
||||||
|
|
||||||
@@ -163,8 +352,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
|
|
||||||
String message = String.join(" ", Arrays.copyOfRange(args, msgStart, 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()) { plugin.lang().send(player, "create.no-description"); return; }
|
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; }
|
if (message.length() > maxLen) { plugin.lang().send(player, "create.too-long", "{max}", String.valueOf(maxLen)); return; }
|
||||||
|
|
||||||
final ConfigCategory fCat = category;
|
final ConfigCategory fCat = category;
|
||||||
final TicketPriority fPrio = priority;
|
final TicketPriority fPrio = priority;
|
||||||
@@ -535,17 +724,12 @@ 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;
|
||||||
}
|
}
|
||||||
// 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.reloadConfig();
|
||||||
plugin.lang().reload();
|
plugin.lang().reload();
|
||||||
plugin.reloadSettings(); // serverName & debug-Flag aktualisieren
|
plugin.reloadSettings();
|
||||||
plugin.getTicketGUI().reloadConfig(); // Slots, Rows & Materialien neu laden
|
plugin.getTicketGUI().reloadConfig();
|
||||||
plugin.getTicketGUI().reloadTitles(); // Inventar-Titel aktualisieren
|
plugin.getTicketGUI().reloadTitles();
|
||||||
plugin.getFaqGUI().reloadConfig(); // FAQ-Slots, Rows & Materialien neu laden
|
plugin.getFaqGUI().reloadConfig();
|
||||||
plugin.getCategoryManager().reload();
|
plugin.getCategoryManager().reload();
|
||||||
plugin.getFaqManager().reload();
|
plugin.getFaqManager().reload();
|
||||||
plugin.getTicketCache().clear();
|
plugin.getTicketCache().clear();
|
||||||
@@ -565,8 +749,7 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
java.io.File backup = plugin.getDatabaseManager().createBackup();
|
java.io.File backup = plugin.getDatabaseManager().createBackup();
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
if (backup != null) {
|
if (backup != null) {
|
||||||
player.sendMessage(plugin.lang().format("backup.success",
|
player.sendMessage(plugin.lang().format("backup.success", "{file}", backup.getName()));
|
||||||
"{file}", backup.getName()));
|
|
||||||
} else {
|
} else {
|
||||||
player.sendMessage(plugin.lang().get("backup.fail"));
|
player.sendMessage(plugin.lang().get("backup.fail"));
|
||||||
}
|
}
|
||||||
@@ -761,6 +944,12 @@ 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)) {
|
||||||
|
plugin.lang().send(player, "faq.disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
plugin.getFaqGUI().openFaqGUI(player);
|
plugin.getFaqGUI().openFaqGUI(player);
|
||||||
return;
|
return;
|
||||||
@@ -777,7 +966,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
player.sendMessage(plugin.lang().get("faq.usage-add-example")); return;
|
player.sendMessage(plugin.lang().get("faq.usage-add-example")); return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prüfen ob args[2] ein bekannter FAQ-Kategorie-Schlüssel ist
|
|
||||||
String faqCatKey = null;
|
String faqCatKey = null;
|
||||||
int textStart = 2;
|
int textStart = 2;
|
||||||
if (plugin.getFaqManager().hasCategoriesEnabled()) {
|
if (plugin.getFaqManager().hasCategoriesEnabled()) {
|
||||||
@@ -861,11 +1049,9 @@ 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;
|
||||||
}
|
}
|
||||||
// Richtung bestimmen: tofile oder tomysql (Standard)
|
|
||||||
String direction = args.length >= 3 ? args[2].toLowerCase() : "tomysql";
|
String direction = args.length >= 3 ? args[2].toLowerCase() : "tomysql";
|
||||||
|
|
||||||
if (direction.equals("tofile") || direction.equals("zudatei")) {
|
if (direction.equals("tofile") || direction.equals("zudatei")) {
|
||||||
// MySQL → faqs.yml
|
|
||||||
if (!plugin.getFaqManager().isUsingMySQL()) {
|
if (!plugin.getFaqManager().isUsingMySQL()) {
|
||||||
player.sendMessage(plugin.lang().get("faq.migrate-no-mysql")); return;
|
player.sendMessage(plugin.lang().get("faq.migrate-no-mysql")); return;
|
||||||
}
|
}
|
||||||
@@ -883,7 +1069,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// faqs.yml → MySQL
|
|
||||||
if (!plugin.getFaqManager().isUsingMySQL()) {
|
if (!plugin.getFaqManager().isUsingMySQL()) {
|
||||||
player.sendMessage(plugin.lang().get("faq.migrate-no-mysql")); return;
|
player.sendMessage(plugin.lang().get("faq.migrate-no-mysql")); return;
|
||||||
}
|
}
|
||||||
@@ -931,19 +1116,16 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── /ticket kategorie (category) ─────────────────────────────────────
|
// ── /ticket kategorie ─────────────────────────────────────────────────
|
||||||
//
|
|
||||||
// /ticket kategorie add <Name> [Farbe] [Beschreibung]
|
|
||||||
// Farbe optional, z.B. &b (Standard: &7)
|
|
||||||
// Beschreibung optional, alles nach der Farbe
|
|
||||||
//
|
|
||||||
// /ticket kategorie delete <Schlüssel>
|
|
||||||
// /ticket kategorie list
|
|
||||||
|
|
||||||
private void handleFaqCategory(Player player, String[] args) {
|
private void handleFaqCategory(Player player, String[] args) {
|
||||||
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)) {
|
||||||
|
plugin.lang().send(player, "faq.disabled"); return;
|
||||||
|
}
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
player.sendMessage(plugin.lang().get("faqcat.usage")); return;
|
player.sendMessage(plugin.lang().get("faqcat.usage")); return;
|
||||||
}
|
}
|
||||||
@@ -951,7 +1133,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
switch (args[1].toLowerCase()) {
|
switch (args[1].toLowerCase()) {
|
||||||
|
|
||||||
case "add", "hinzufügen", "hinzufuegen" -> {
|
case "add", "hinzufügen", "hinzufuegen" -> {
|
||||||
// /ticket kategorie add <Name> [&Farbe] [Beschreibung...]
|
|
||||||
if (args.length < 3) {
|
if (args.length < 3) {
|
||||||
player.sendMessage(plugin.lang().get("faqcat.usage-add")); return;
|
player.sendMessage(plugin.lang().get("faqcat.usage-add")); return;
|
||||||
}
|
}
|
||||||
@@ -1069,9 +1250,25 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
@Override
|
@Override
|
||||||
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
|
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
|
|
||||||
|
// Konsolen-Tab-Completion
|
||||||
|
if (sender instanceof ConsoleCommandSender) {
|
||||||
|
if (args.length == 1) {
|
||||||
|
for (String s : List.of("create", "reload", "archive", "stats"))
|
||||||
|
if (s.startsWith(args[0].toLowerCase())) completions.add(s);
|
||||||
|
} else if (args.length == 2 && normalize(args[0]).equals("create")) {
|
||||||
|
// Spielernamen vorschlagen
|
||||||
|
for (Player p : Bukkit.getOnlinePlayers())
|
||||||
|
if (p.getName().toLowerCase().startsWith(args[1].toLowerCase())) completions.add(p.getName());
|
||||||
|
} else if (args.length == 3 && normalize(args[0]).equals("create")) {
|
||||||
|
// Kategorien vorschlagen
|
||||||
|
completions.addAll(plugin.getCategoryManager().getDisplayNamesForTabComplete(args[2]));
|
||||||
|
}
|
||||||
|
return completions;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(sender instanceof Player player)) return completions;
|
if (!(sender instanceof Player player)) return completions;
|
||||||
|
|
||||||
// Nur die in der Config eingestellte Sprache verwenden
|
|
||||||
final boolean useDe = plugin.lang().acceptsGerman();
|
final boolean useDe = plugin.lang().acceptsGerman();
|
||||||
final boolean useEn = plugin.lang().acceptsEnglish();
|
final boolean useEn = plugin.lang().acceptsEnglish();
|
||||||
|
|
||||||
@@ -1091,11 +1288,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
if (useDe) subs.add("priorität");
|
if (useDe) subs.add("priorität");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.hasPermission("ticket.admin")) {
|
|
||||||
if (useEn) subs.addAll(List.of("faq"));
|
|
||||||
if (useDe) subs.addAll(List.of("faq"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player.hasPermission("ticket.admin")) {
|
if (player.hasPermission("ticket.admin")) {
|
||||||
subs.add("kategorie");
|
subs.add("kategorie");
|
||||||
if (useEn) subs.add("category");
|
if (useEn) subs.add("category");
|
||||||
@@ -1123,7 +1315,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
&& (args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("hinzufügen")
|
&& (args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("hinzufügen")
|
||||||
|| args[1].equalsIgnoreCase("hinzufuegen"))
|
|| args[1].equalsIgnoreCase("hinzufuegen"))
|
||||||
&& player.hasPermission("ticket.admin")) {
|
&& player.hasPermission("ticket.admin")) {
|
||||||
// /ticket faq add <TAB> → FAQ-Kategorie-Schlüssel vorschlagen
|
|
||||||
completions.addAll(getFaqCategoryKeysForTab(args[2]));
|
completions.addAll(getFaqCategoryKeysForTab(args[2]));
|
||||||
|
|
||||||
} else if (args.length == 3 && normalize(args[0]).equals("faq")
|
} else if (args.length == 3 && normalize(args[0]).equals("faq")
|
||||||
@@ -1136,7 +1327,6 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
} else if (args.length == 2 && (normalize(args[0]).equals("category")
|
} else if (args.length == 2 && (normalize(args[0]).equals("category")
|
||||||
|| args[0].equalsIgnoreCase("kategorie"))
|
|| args[0].equalsIgnoreCase("kategorie"))
|
||||||
&& player.hasPermission("ticket.admin")) {
|
&& player.hasPermission("ticket.admin")) {
|
||||||
// /ticket kategorie <TAB>
|
|
||||||
if (useEn) completions.addAll(List.of("add", "delete", "list"));
|
if (useEn) completions.addAll(List.of("add", "delete", "list"));
|
||||||
if (useDe) completions.addAll(List.of("hinzufügen", "löschen", "liste"));
|
if (useDe) completions.addAll(List.of("hinzufügen", "löschen", "liste"));
|
||||||
completions.removeIf(s -> !s.startsWith(args[1].toLowerCase()));
|
completions.removeIf(s -> !s.startsWith(args[1].toLowerCase()));
|
||||||
@@ -1146,21 +1336,17 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
&& (args[1].equalsIgnoreCase("delete") || args[1].equalsIgnoreCase("remove")
|
&& (args[1].equalsIgnoreCase("delete") || args[1].equalsIgnoreCase("remove")
|
||||||
|| args[1].equalsIgnoreCase("löschen") || args[1].equalsIgnoreCase("loeschen"))
|
|| args[1].equalsIgnoreCase("löschen") || args[1].equalsIgnoreCase("loeschen"))
|
||||||
&& player.hasPermission("ticket.admin")) {
|
&& player.hasPermission("ticket.admin")) {
|
||||||
// /ticket kategorie delete <TAB> → vorhandene Schlüssel
|
|
||||||
completions.addAll(getFaqCategoryKeysForTab(args[2]));
|
completions.addAll(getFaqCategoryKeysForTab(args[2]));
|
||||||
|
|
||||||
} else if (args.length == 2 && normalize(args[0]).equals("create")) {
|
} else if (args.length == 2 && normalize(args[0]).equals("create")) {
|
||||||
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);
|
||||||
|
|
||||||
// Wenn Kategorien aktiviert: zeige nur Kategorien-Anzeigenamen bei args[1]
|
|
||||||
if (categoriesOn) {
|
if (categoriesOn) {
|
||||||
completions.addAll(plugin.getCategoryManager().getDisplayNamesForTabComplete(args[1]));
|
completions.addAll(plugin.getCategoryManager().getDisplayNamesForTabComplete(args[1]));
|
||||||
} else if (prioritiesOn) {
|
} else if (prioritiesOn) {
|
||||||
// Wenn nur Prioritäten aktiviert (keine Kategorien): zeige Prioritäten bei args[1]
|
|
||||||
boolean allowPlayersPrio = plugin.getConfig().getBoolean("allow-players-to-set-priority", false);
|
boolean allowPlayersPrio = plugin.getConfig().getBoolean("allow-players-to-set-priority", false);
|
||||||
boolean isTeam = sender instanceof Player p &&
|
boolean isTeam = player.hasPermission("ticket.support") || player.hasPermission("ticket.admin");
|
||||||
(p.hasPermission("ticket.support") || p.hasPermission("ticket.admin"));
|
|
||||||
if (allowPlayersPrio || isTeam) {
|
if (allowPlayersPrio || isTeam) {
|
||||||
completions.addAll(getPriorityInputsForTab(args[1]));
|
completions.addAll(getPriorityInputsForTab(args[1]));
|
||||||
}
|
}
|
||||||
@@ -1169,12 +1355,10 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
} else if (args.length == 3 && normalize(args[0]).equals("create")) {
|
} else if (args.length == 3 && normalize(args[0]).equals("create")) {
|
||||||
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);
|
||||||
|
|
||||||
// Wenn beide aktiviert: args[2] = Priorität
|
|
||||||
if (categoriesOn && prioritiesOn) {
|
if (categoriesOn && prioritiesOn) {
|
||||||
boolean allowPlayersPrio = plugin.getConfig().getBoolean("allow-players-to-set-priority", false);
|
boolean allowPlayersPrio = plugin.getConfig().getBoolean("allow-players-to-set-priority", false);
|
||||||
boolean isTeam = sender instanceof Player p &&
|
boolean isTeam = player.hasPermission("ticket.support") || player.hasPermission("ticket.admin");
|
||||||
(p.hasPermission("ticket.support") || p.hasPermission("ticket.admin"));
|
|
||||||
if (allowPlayersPrio || isTeam) {
|
if (allowPlayersPrio || isTeam) {
|
||||||
completions.addAll(getPriorityInputsForTab(args[2]));
|
completions.addAll(getPriorityInputsForTab(args[2]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
package de.ticketsystem.manager;
|
package de.ticketsystem.manager;
|
||||||
|
|
||||||
import de.ticketsystem.TicketPlugin;
|
import de.ticketsystem.TicketPlugin;
|
||||||
|
import net.kyori.adventure.audience.Audience;
|
||||||
|
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -20,8 +26,13 @@ import java.util.regex.Pattern;
|
|||||||
* Lädt alle Plugin-Texte aus der aktiven Sprachdatei (lang_de.yml / lang_en.yml)
|
* Lädt alle Plugin-Texte aus der aktiven Sprachdatei (lang_de.yml / lang_en.yml)
|
||||||
* und ersetzt {cmd_X}-Platzhalter durch die passenden Befehlsnamen.
|
* und ersetzt {cmd_X}-Platzhalter durch die passenden Befehlsnamen.
|
||||||
*
|
*
|
||||||
* Unterstützt Hex-Farbcodes (z.B. &#FF0055 oder <#FF0055>).
|
* Unterstützt:
|
||||||
* Funktioniert ab Spigot 1.16+.
|
* - &-Farbcodes und Hex-Farbcodes (&#RRGGBB oder <#RRGGBB>)
|
||||||
|
* - MiniMessage-Tags wie <click:run_command:...>, <hover:show_text:...>, <bold> etc.
|
||||||
|
* → Texte mit MiniMessage-Tags werden als Adventure-Component gesendet,
|
||||||
|
* damit click/hover-Events tatsächlich funktionieren.
|
||||||
|
*
|
||||||
|
* Funktioniert ab Paper/Spigot 1.16+ (Adventure-API vorausgesetzt, ab 1.18 eingebaut).
|
||||||
*
|
*
|
||||||
* ┌─────────────────────────────────────────────────────────┐
|
* ┌─────────────────────────────────────────────────────────┐
|
||||||
* │ Einziger Konfigurations-Schlüssel: language │
|
* │ Einziger Konfigurations-Schlüssel: language │
|
||||||
@@ -29,9 +40,6 @@ import java.util.regex.Pattern;
|
|||||||
* │ language: de → deutsche Texte + deutsche Befehle │
|
* │ language: de → deutsche Texte + deutsche Befehle │
|
||||||
* │ language: en → englische Texte + englische Befehle │
|
* │ language: en → englische Texte + englische Befehle │
|
||||||
* │ language: both → deutsche Texte + beide Befehlsnamen │
|
* │ language: both → deutsche Texte + beide Befehlsnamen │
|
||||||
* │ │
|
|
||||||
* │ "command-language" existiert nicht mehr und wird │
|
|
||||||
* │ vollständig ignoriert. │
|
|
||||||
* └─────────────────────────────────────────────────────────┘
|
* └─────────────────────────────────────────────────────────┘
|
||||||
*
|
*
|
||||||
* Verfügbare {cmd_X}-Platzhalter in lang.yml:
|
* Verfügbare {cmd_X}-Platzhalter in lang.yml:
|
||||||
@@ -53,6 +61,17 @@ public class LanguageManager {
|
|||||||
"blacklist.list-", "gui.", "join.pending-header", "update."
|
"blacklist.list-", "gui.", "join.pending-header", "update."
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erkennt MiniMessage-Tags, die interaktive Inhalte enthalten:
|
||||||
|
* click, hover, insertion. Nur wenn solche Tags vorhanden sind,
|
||||||
|
* wird die Nachricht als Component (statt als Legacy-String) gesendet.
|
||||||
|
*/
|
||||||
|
private static final Pattern MINIMESSAGE_INTERACTIVE =
|
||||||
|
Pattern.compile("<(?:click|hover|insertion)[^>]*>", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
/** MiniMessage-Instanz (singleton, thread-safe). */
|
||||||
|
private static final MiniMessage MINI = MiniMessage.miniMessage();
|
||||||
|
|
||||||
// ── Befehlsnamen-Tabellen (statisch, ändern sich nie) ───────────────────
|
// ── Befehlsnamen-Tabellen (statisch, ändern sich nie) ───────────────────
|
||||||
|
|
||||||
private static final LinkedHashMap<String, String> DE = new LinkedHashMap<>();
|
private static final LinkedHashMap<String, String> DE = new LinkedHashMap<>();
|
||||||
@@ -104,7 +123,6 @@ public class LanguageManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Aktiver Sprachmodus – wird bei jedem load() DIREKT aus der Config gelesen.
|
* Aktiver Sprachmodus – wird bei jedem load() DIREKT aus der Config gelesen.
|
||||||
* Kein Cache, kein Zwischenwert. Immer frisch nach reloadConfig().
|
|
||||||
*/
|
*/
|
||||||
private String activeLang;
|
private String activeLang;
|
||||||
|
|
||||||
@@ -120,23 +138,26 @@ public class LanguageManager {
|
|||||||
*/
|
*/
|
||||||
private Map<String, String> cmdNames = new LinkedHashMap<>();
|
private Map<String, String> cmdNames = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BukkitAudiences – Brücke zwischen Adventure-Components und Spigot.
|
||||||
|
* Muss beim Plugin-onDisable via audiences.close() geschlossen werden.
|
||||||
|
* Siehe TicketPlugin.onDisable().
|
||||||
|
*/
|
||||||
|
private final BukkitAudiences audiences;
|
||||||
|
|
||||||
// ── Konstruktor ──────────────────────────────────────────────────────────
|
// ── Konstruktor ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public LanguageManager(TicketPlugin plugin) {
|
public LanguageManager(TicketPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.audiences = BukkitAudiences.create(plugin);
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Laden ────────────────────────────────────────────────────────────────
|
// ── Laden ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
|
||||||
* Lädt (oder relädt) die Sprachdatei und baut alle Befehlsnamen neu.
|
|
||||||
* Muss nach plugin.reloadConfig() aufgerufen werden, damit die frische
|
|
||||||
* language-Einstellung übernommen wird.
|
|
||||||
*/
|
|
||||||
public void load() {
|
public void load() {
|
||||||
|
|
||||||
// 1. language aus der (bereits neu geladenen) Config lesen
|
// 1. language aus der Config lesen
|
||||||
String raw = plugin.getConfig().getString("language", FALLBACK)
|
String raw = plugin.getConfig().getString("language", FALLBACK)
|
||||||
.toLowerCase().trim();
|
.toLowerCase().trim();
|
||||||
|
|
||||||
@@ -204,20 +225,13 @@ public class LanguageManager {
|
|||||||
|
|
||||||
// ── Befehlsnamen ─────────────────────────────────────────────────────────
|
// ── Befehlsnamen ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
|
||||||
* Baut {cmd_X} → Anzeigename anhand von activeLang.
|
|
||||||
*
|
|
||||||
* de → /ticket erstellen
|
|
||||||
* en → /ticket create
|
|
||||||
* both → /ticket create §8(§7erstellen§8)
|
|
||||||
*/
|
|
||||||
private Map<String, String> buildCmdNames() {
|
private Map<String, String> buildCmdNames() {
|
||||||
Map<String, String> map = new LinkedHashMap<>();
|
Map<String, String> map = new LinkedHashMap<>();
|
||||||
for (String key : EN.keySet()) {
|
for (String key : EN.keySet()) {
|
||||||
String display = switch (activeLang) {
|
String display = switch (activeLang) {
|
||||||
case "en" -> "/ticket " + EN.get(key);
|
case "en" -> "/ticket " + EN.get(key);
|
||||||
case "both" -> "/ticket " + EN.get(key) + " §8(§7" + DE.get(key) + "§8)";
|
case "both" -> "/ticket " + EN.get(key) + " §8(§7" + DE.get(key) + "§8)";
|
||||||
default -> "/ticket " + DE.get(key); // "de" + alle unbekannten
|
default -> "/ticket " + DE.get(key);
|
||||||
};
|
};
|
||||||
map.put("{cmd_" + key + "}", display);
|
map.put("{cmd_" + key + "}", display);
|
||||||
}
|
}
|
||||||
@@ -232,12 +246,9 @@ public class LanguageManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Befehlssprache-Abfragen (für TicketCommand) ──────────────────────────
|
// ── Befehlssprache-Abfragen ──────────────────────────────────────────────
|
||||||
|
|
||||||
/** true wenn deutsche Subkommandos akzeptiert werden sollen (Tab-Complete & Eingabe). */
|
|
||||||
public boolean acceptsGerman() { return "de".equals(activeLang) || "both".equals(activeLang); }
|
public boolean acceptsGerman() { return "de".equals(activeLang) || "both".equals(activeLang); }
|
||||||
|
|
||||||
/** true wenn englische Subkommandos akzeptiert werden sollen (Tab-Complete & Eingabe). */
|
|
||||||
public boolean acceptsEnglish() { return "en".equals(activeLang) || "both".equals(activeLang); }
|
public boolean acceptsEnglish() { return "en".equals(activeLang) || "both".equals(activeLang); }
|
||||||
|
|
||||||
// ── Interne Platzhalter-Ersetzung ───────────────────────────────────────
|
// ── Interne Platzhalter-Ersetzung ───────────────────────────────────────
|
||||||
@@ -281,11 +292,48 @@ public class LanguageManager {
|
|||||||
return prefix + format(key, replacements);
|
return prefix + format(key, replacements);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sendet eine Nachricht (mit Prefix wenn nötig) an einen CommandSender. */
|
/**
|
||||||
|
* Sendet eine Nachricht an einen CommandSender.
|
||||||
|
*
|
||||||
|
* Enthält der Rohtext MiniMessage-Tags mit interaktiven Elementen
|
||||||
|
* (click, hover, insertion), wird die Nachricht als Adventure-Component
|
||||||
|
* gesendet – damit run_command, suggest_command und hover-Texte
|
||||||
|
* tatsächlich funktionieren.
|
||||||
|
*
|
||||||
|
* Enthält er keine solchen Tags, wird der klassische Legacy-Pfad
|
||||||
|
* (§-Codes) verwendet – volle Abwärtskompatibilität.
|
||||||
|
*/
|
||||||
public void send(CommandSender sender, String key, String... replacements) {
|
public void send(CommandSender sender, String key, String... replacements) {
|
||||||
sender.sendMessage(needsPrefix(key)
|
String raw = applyCmdNames(getRaw(key));
|
||||||
? prefix + format(key, replacements)
|
|
||||||
: format(key, replacements));
|
// {placeholder}-Ersetzungen anwenden (vor MiniMessage-Parsing!)
|
||||||
|
if (replacements.length % 2 != 0)
|
||||||
|
plugin.getLogger().warning("[LanguageManager] send() benötigt eine gerade Anzahl an Argumenten für: " + key);
|
||||||
|
for (int i = 0; i + 1 < replacements.length; i += 2)
|
||||||
|
raw = raw.replace(replacements[i], replacements[i + 1]);
|
||||||
|
|
||||||
|
if (hasMiniMessageInteractiveTags(raw)) {
|
||||||
|
// MiniMessage-Pfad: interaktive Komponenten (click/hover)
|
||||||
|
// Legacy-&-Farbcodes in MiniMessage-Äquivalente umwandeln damit
|
||||||
|
// beide Syntax-Varianten in derselben Nachricht funktionieren.
|
||||||
|
String mm = legacyToMiniMessage(raw);
|
||||||
|
Component component = MINI.deserialize(mm);
|
||||||
|
|
||||||
|
// Prefix als Component voranstellen wenn nötig
|
||||||
|
if (needsPrefix(key)) {
|
||||||
|
Component prefixComp = MINI.deserialize(legacyToMiniMessage(
|
||||||
|
color("&8[&6Ticket&8] &r")));
|
||||||
|
component = prefixComp.append(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BukkitAudiences sendet korrekt auf Spigot UND Paper
|
||||||
|
Audience audience = audiences.sender(sender);
|
||||||
|
audience.sendMessage(component);
|
||||||
|
} else {
|
||||||
|
// Legacy-Pfad: §-Codes, kein Adventure nötig
|
||||||
|
String text = color(raw);
|
||||||
|
sender.sendMessage(needsPrefix(key) ? prefix + text : text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sendet die Trennlinie. */
|
/** Sendet die Trennlinie. */
|
||||||
@@ -304,11 +352,12 @@ public class LanguageManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Übersetzt &-Farbcodes und Hex-Farbcodes (&#RRGGBB oder <#RRGGBB>) in §-Codes.
|
* Übersetzt &-Farbcodes und Hex-Farbcodes (&#RRGGBB oder <#RRGGBB>) in §-Codes.
|
||||||
|
* Wird für alle Texte verwendet die NICHT als Component gesendet werden.
|
||||||
*/
|
*/
|
||||||
public String color(String text) {
|
public String color(String text) {
|
||||||
if (text == null || text.isEmpty()) return "";
|
if (text == null || text.isEmpty()) return "";
|
||||||
|
|
||||||
// Regex für Hex Codes: &#RRGGBB oder <#RRGGBB>
|
// Hex-Codes: &#RRGGBB oder <#RRGGBB>
|
||||||
Pattern hexPattern = Pattern.compile("&#([A-Fa-f0-9]{6})|<#([A-Fa-f0-9]{6})>");
|
Pattern hexPattern = Pattern.compile("&#([A-Fa-f0-9]{6})|<#([A-Fa-f0-9]{6})>");
|
||||||
Matcher matcher = hexPattern.matcher(text);
|
Matcher matcher = hexPattern.matcher(text);
|
||||||
StringBuffer buffer = new StringBuffer();
|
StringBuffer buffer = new StringBuffer();
|
||||||
@@ -316,29 +365,126 @@ public class LanguageManager {
|
|||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String group = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
|
String group = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
|
||||||
try {
|
try {
|
||||||
// Fix: Explizite Nutzung von net.md_5.bungee.api.ChatColor für Hex-Support (Spigot 1.16+)
|
|
||||||
// Dies verhindert den "Symbol nicht gefunden"-Fehler beim Kompilieren mit der reinen Bukkit-API.
|
|
||||||
net.md_5.bungee.api.ChatColor hexColor = net.md_5.bungee.api.ChatColor.of("#" + group);
|
net.md_5.bungee.api.ChatColor hexColor = net.md_5.bungee.api.ChatColor.of("#" + group);
|
||||||
matcher.appendReplacement(buffer, hexColor.toString());
|
matcher.appendReplacement(buffer, hexColor.toString());
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
// Falls der Farbcode ungültig ist, Tag entfernen
|
|
||||||
matcher.appendReplacement(buffer, "");
|
matcher.appendReplacement(buffer, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String parsed = matcher.appendTail(buffer).toString();
|
String parsed = matcher.appendTail(buffer).toString();
|
||||||
|
|
||||||
// Übersetzung der klassischen &-Farbcodes (Bukkit Standard)
|
|
||||||
return org.bukkit.ChatColor.translateAlternateColorCodes('&', parsed);
|
return org.bukkit.ChatColor.translateAlternateColorCodes('&', parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPrefix() { return prefix; }
|
public String getPrefix() { return prefix; }
|
||||||
public String getActiveLang() { return activeLang; }
|
public String getActiveLang() { return activeLang; }
|
||||||
public String getFileLang() { return fileLang; }
|
public String getFileLang() { return fileLang; }
|
||||||
|
public BukkitAudiences getAudiences() { return audiences; }
|
||||||
|
|
||||||
/** Relädt die Sprachdatei. Muss NACH plugin.reloadConfig() aufgerufen werden. */
|
/** Relädt die Sprachdatei. Muss NACH plugin.reloadConfig() aufgerufen werden. */
|
||||||
public void reload() { load(); }
|
public void reload() { load(); }
|
||||||
|
|
||||||
|
// ── MiniMessage-Hilfsmethoden ────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft ob ein Text MiniMessage-Tags mit interaktiven Inhalten enthält
|
||||||
|
* (click, hover, insertion). Nur dann lohnt sich der Component-Pfad.
|
||||||
|
*/
|
||||||
|
private boolean hasMiniMessageInteractiveTags(String text) {
|
||||||
|
if (text == null) return false;
|
||||||
|
return MINIMESSAGE_INTERACTIVE.matcher(text).find();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertiert &-Farbcodes und &#RRGGBB/<#RRGGBB>-Hex-Codes in MiniMessage-Syntax,
|
||||||
|
* damit ein Text der beide Formate mischt korrekt von MiniMessage geparst werden kann.
|
||||||
|
*
|
||||||
|
* Beispiele:
|
||||||
|
* &6Hallo → <gold>Hallo
|
||||||
|
* &#FF5500Text → <#FF5500>Text
|
||||||
|
* &lFett → <bold>Fett
|
||||||
|
* &rReset → <reset>Reset
|
||||||
|
*/
|
||||||
|
private String legacyToMiniMessage(String text) {
|
||||||
|
if (text == null) return "";
|
||||||
|
|
||||||
|
// 1. &#RRGGBB → <#RRGGBB>
|
||||||
|
text = text.replaceAll("&#([A-Fa-f0-9]{6})", "<#$1>");
|
||||||
|
|
||||||
|
// 2. §-Codes (bereits konvertierte Hex-Codes §x§R§G§B... überspringen wir hier,
|
||||||
|
// da sie nicht in MiniMessage passen – nur &-Shorthand-Codes mappen)
|
||||||
|
// Wir ersetzen &X-Codes durch ihre MiniMessage-Äquivalente.
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
char[] chars = text.toCharArray();
|
||||||
|
for (int i = 0; i < chars.length; i++) {
|
||||||
|
if ((chars[i] == '&' || chars[i] == '§') && i + 1 < chars.length) {
|
||||||
|
char code = Character.toLowerCase(chars[i + 1]);
|
||||||
|
String mm = switch (code) {
|
||||||
|
case '0' -> "<black>";
|
||||||
|
case '1' -> "<dark_blue>";
|
||||||
|
case '2' -> "<dark_green>";
|
||||||
|
case '3' -> "<dark_aqua>";
|
||||||
|
case '4' -> "<dark_red>";
|
||||||
|
case '5' -> "<dark_purple>";
|
||||||
|
case '6' -> "<gold>";
|
||||||
|
case '7' -> "<gray>";
|
||||||
|
case '8' -> "<dark_gray>";
|
||||||
|
case '9' -> "<blue>";
|
||||||
|
case 'a' -> "<green>";
|
||||||
|
case 'b' -> "<aqua>";
|
||||||
|
case 'c' -> "<red>";
|
||||||
|
case 'd' -> "<light_purple>";
|
||||||
|
case 'e' -> "<yellow>";
|
||||||
|
case 'f' -> "<white>";
|
||||||
|
case 'l' -> "<bold>";
|
||||||
|
case 'm' -> "<strikethrough>";
|
||||||
|
case 'n' -> "<underlined>";
|
||||||
|
case 'o' -> "<italic>";
|
||||||
|
case 'r' -> "<reset>";
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
if (mm != null) {
|
||||||
|
sb.append(mm);
|
||||||
|
i++; // Code-Zeichen überspringen
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(chars[i]);
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -209,6 +209,9 @@ discord:
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
# GUI KONFIGURATION (Layouts, Slots, Items)
|
# GUI KONFIGURATION (Layouts, Slots, Items)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
# FAQ-System global deaktivieren (Standard: true)
|
||||||
|
faq-enabled: true
|
||||||
|
|
||||||
# Hier kannst du das Aussehen und die Anordnung der GUIs anpassen.
|
# Hier kannst du das Aussehen und die Anordnung der GUIs anpassen.
|
||||||
# WICHTIG: gui-settings muss ganz links stehen (keine Raute davor!).
|
# WICHTIG: gui-settings muss ganz links stehen (keine Raute davor!).
|
||||||
gui-settings:
|
gui-settings:
|
||||||
|
|||||||
@@ -576,7 +576,6 @@ web:
|
|||||||
login-blocked: "Zu viele Fehlversuche. Bitte warte {seconds} Sekunden."
|
login-blocked: "Zu viele Fehlversuche. Bitte warte {seconds} Sekunden."
|
||||||
archive-btn-restore: "Wiederherstellen"
|
archive-btn-restore: "Wiederherstellen"
|
||||||
archive-btn-delete: "Permanent löschen"
|
archive-btn-delete: "Permanent löschen"
|
||||||
detail-btn-archive: "Ins Archiv verschieben"
|
|
||||||
login-label-user: "Benutzername"
|
login-label-user: "Benutzername"
|
||||||
login-label-pass: "Passwort"
|
login-label-pass: "Passwort"
|
||||||
login-btn: "Anmelden"
|
login-btn: "Anmelden"
|
||||||
@@ -615,13 +614,11 @@ web:
|
|||||||
tickets-col-prio: "Priorität"
|
tickets-col-prio: "Priorität"
|
||||||
tickets-col-status: "Status"
|
tickets-col-status: "Status"
|
||||||
tickets-col-created: "Erstellt"
|
tickets-col-created: "Erstellt"
|
||||||
nav-archive: "Archiv"
|
|
||||||
filter-all-status: "Alle Status"
|
filter-all-status: "Alle Status"
|
||||||
filter-open: "Offen"
|
filter-open: "Offen"
|
||||||
filter-claimed: "Angenommen"
|
filter-claimed: "Angenommen"
|
||||||
filter-forwarded: "Weitergeleitet"
|
filter-forwarded: "Weitergeleitet"
|
||||||
filter-closed: "Geschlossen"
|
filter-closed: "Geschlossen"
|
||||||
filter-archived: "Archiviert"
|
|
||||||
filter-all-cat: "Alle Kategorien"
|
filter-all-cat: "Alle Kategorien"
|
||||||
filter-all-prio: "Alle Prioritäten"
|
filter-all-prio: "Alle Prioritäten"
|
||||||
filter-low: "Niedrig"
|
filter-low: "Niedrig"
|
||||||
|
|||||||
@@ -576,7 +576,6 @@ web:
|
|||||||
login-error: "Username or password incorrect."
|
login-error: "Username or password incorrect."
|
||||||
archive-btn-restore: "Restore"
|
archive-btn-restore: "Restore"
|
||||||
archive-btn-delete: "Delete permanently"
|
archive-btn-delete: "Delete permanently"
|
||||||
detail-btn-archive: "Move to archive"
|
|
||||||
login-label-user: "Username"
|
login-label-user: "Username"
|
||||||
login-label-pass: "Password"
|
login-label-pass: "Password"
|
||||||
login-btn: "Sign In"
|
login-btn: "Sign In"
|
||||||
@@ -615,13 +614,11 @@ web:
|
|||||||
tickets-col-prio: "Priority"
|
tickets-col-prio: "Priority"
|
||||||
tickets-col-status: "Status"
|
tickets-col-status: "Status"
|
||||||
tickets-col-created: "Created"
|
tickets-col-created: "Created"
|
||||||
nav-archive: "Archive"
|
|
||||||
filter-all-status: "All Statuses"
|
filter-all-status: "All Statuses"
|
||||||
filter-open: "Open"
|
filter-open: "Open"
|
||||||
filter-claimed: "Claimed"
|
filter-claimed: "Claimed"
|
||||||
filter-forwarded: "Forwarded"
|
filter-forwarded: "Forwarded"
|
||||||
filter-closed: "Closed"
|
filter-closed: "Closed"
|
||||||
filter-archived: "Archived"
|
|
||||||
filter-all-cat: "All Categories"
|
filter-all-cat: "All Categories"
|
||||||
filter-all-prio: "All Priorities"
|
filter-all-prio: "All Priorities"
|
||||||
filter-low: "Low"
|
filter-low: "Low"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: TicketSystem
|
name: TicketSystem
|
||||||
version: 1.1.4
|
version: 1.1.5
|
||||||
main: de.ticketsystem.TicketPlugin
|
main: de.ticketsystem.TicketPlugin
|
||||||
api-version: 1.20
|
api-version: 1.20
|
||||||
author: M_Viper
|
author: M_Viper
|
||||||
|
|||||||
Reference in New Issue
Block a user