package de.ticketsystem; import de.ticketsystem.bungee.BungeeMessenger; import de.ticketsystem.cache.TicketCache; import de.ticketsystem.commands.TicketCommand; import de.ticketsystem.database.DatabaseManager; import de.ticketsystem.discord.DiscordWebhook; import de.ticketsystem.gui.FaqGUI; import de.ticketsystem.gui.TicketGUI; import de.ticketsystem.listeners.PlayerJoinListener; import de.ticketsystem.manager.CategoryManager; import de.ticketsystem.manager.FaqManager; import de.ticketsystem.manager.LanguageManager; import de.ticketsystem.manager.TicketManager; import de.ticketsystem.model.Ticket; import de.ticketsystem.model.TicketPriority; import de.ticketsystem.web.SessionManager; import de.ticketsystem.web.WebServer; import org.bukkit.ChatColor; import org.bukkit.plugin.java.JavaPlugin; import java.util.Objects; public class TicketPlugin extends JavaPlugin { private static TicketPlugin instance; private boolean debug; /** * Name dieses Servers im BungeeCord-Netzwerk. * Konfigurierbar in config.yml → server-name */ private String serverName; private LanguageManager languageManager; private DatabaseManager databaseManager; private TicketManager ticketManager; private CategoryManager categoryManager; private FaqManager faqManager; private TicketGUI ticketGUI; private FaqGUI faqGUI; private DiscordWebhook discordWebhook; private BungeeMessenger bungeeMessenger; private TicketCache ticketCache; private SessionManager sessionManager; private WebServer webServer; @Override public void onEnable() { instance = this; saveDefaultConfig(); // Ticket-Klasse für YAML-Serialisierung registrieren Ticket.register(); // ── Sprachdatei laden (lang.yml) ────────────────────────────────── // Muss VOR allen anderen Managern geschehen, da diese lang() nutzen. languageManager = new LanguageManager(this); TicketPriority.reloadLocalizedNames(this); // ── BungeeCord Plugin-Messaging-Kanäle registrieren ─────────────── getServer().getMessenger().registerOutgoingPluginChannel(this, BungeeMessenger.BUNGEE_CHANNEL); getServer().getMessenger().registerOutgoingPluginChannel(this, BungeeMessenger.CUSTOM_CHANNEL); bungeeMessenger = new BungeeMessenger(this); getServer().getMessenger().registerIncomingPluginChannel(this, BungeeMessenger.CUSTOM_CHANNEL, bungeeMessenger); // Server-Name aus Config lesen serverName = getConfig().getString("server-name", "unknown"); if ("unknown".equals(serverName)) { getLogger().warning("[BungeeCord] Kein 'server-name' in der config.yml definiert!"); } // BungeeCord-Hinweis nur bei deaktiviertem Feature und aktivem Debug ausgeben if (getConfig().getBoolean("debug", false) && !getConfig().getBoolean("bungeecord", false)) { getLogger().info("[BungeeCord] Cross-Server-Features deaktiviert. Setze 'bungeecord: true' um sie zu aktivieren."); } // Update-Checker int resourceId = 132757; new UpdateChecker(this, resourceId).getVersion(version -> { String current = getDescription().getVersion(); if (!current.equals(version)) { String msg = lang().format("update.available-line1", "{version}", version); getLogger().warning(lang().format("update.available-console", "{new}", version, "{current}", current)); getServer().getScheduler().runTaskLater(this, () -> getServer().getOnlinePlayers().stream() .filter(p -> p.hasPermission("ticket.admin")) .forEach(p -> p.sendMessage(msg)), 20L); } }); // Versionsprüfung der config.yml String configVersion = getConfig().getString("version", ""); String expectedVersion = "2.5"; if (!expectedVersion.equals(configVersion)) { getLogger().warning("[WARNUNG] config.yml-Version (" + configVersion + ") stimmt nicht mit der erwarteten Version (" + expectedVersion + ") überein!"); } debug = getConfig().getBoolean("debug", false); // ── Performance: Ticket-Cache ────────────────────────────────────── long cacheTtl = getConfig().getLong("cache-ttl-seconds", 60) * 1000L; ticketCache = new TicketCache(cacheTtl); // Regelmäßige Cache-Bereinigung alle 5 Minuten getServer().getScheduler().runTaskTimerAsynchronously(this, () -> ticketCache.evictExpired(), 6000L, 6000L); // Datenbankverbindung databaseManager = new DatabaseManager(this); if (!databaseManager.connect()) { getLogger().severe("Konnte keine Datenbankverbindung herstellen! Plugin läuft im Datei-Modus weiter."); } // Manager, GUI, FAQ & Discord-Webhook initialisieren categoryManager = new CategoryManager(this); ticketManager = new TicketManager(this); faqManager = new FaqManager(this); ticketGUI = new TicketGUI(this); faqGUI = new FaqGUI(this); discordWebhook = new DiscordWebhook(this); if (getConfig().getBoolean("discord.enabled", false)) { String url = getConfig().getString("discord.webhook-url", ""); if (url.isEmpty()) { getLogger().warning("[DiscordWebhook] Aktiviert, aber keine Webhook-URL in config.yml eingetragen!"); } } // Commands & Events TicketCommand ticketCommand = new TicketCommand(this); Objects.requireNonNull(getCommand("ticket")).setExecutor(ticketCommand); Objects.requireNonNull(getCommand("ticket")).setTabCompleter(ticketCommand); getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this); getServer().getPluginManager().registerEvents(ticketGUI, this); getServer().getPluginManager().registerEvents(faqGUI, this); // Automatische Archivierung int archiveIntervalH = getConfig().getInt("auto-archive-interval-hours", 24); if (archiveIntervalH > 0) { long ticks = archiveIntervalH * 60L * 60L * 20L; getServer().getScheduler().runTaskTimer(this, () -> { int archived = databaseManager.archiveClosedTickets(); if (archived > 0) { getLogger().info("Automatische Archivierung: " + archived + " Tickets archiviert."); } }, ticks, ticks); } // Web-Panel starten if (getConfig().getBoolean("web-panel.enabled", false)) { sessionManager = new SessionManager(this); webServer = new WebServer(this, sessionManager); webServer.start(); } getLogger().info("TicketSystem v" + getDescription().getVersion() + " erfolgreich gestartet!"); } @Override public void onDisable() { getServer().getMessenger().unregisterOutgoingPluginChannel(this); getServer().getMessenger().unregisterIncomingPluginChannel(this); if (webServer != null) webServer.stop(); if (ticketCache != null) ticketCache.clear(); if (databaseManager != null) databaseManager.disconnect(); getLogger().info("TicketSystem wurde deaktiviert."); } // ─────────────────────────── Hilfsmethoden ───────────────────────────── /** * Gibt den LanguageManager zurück – bevorzugte Methode für alle Texte. * * Verwendung: * plugin.lang().get("ticket.created") * plugin.lang().format("ticket.created", "{id}", String.valueOf(id)) * plugin.lang().send(player, "ticket.created", "{id}", String.valueOf(id)) */ public LanguageManager lang() { return languageManager; } /** * Kompatibilitätsmethode für bestehenden Code. * Liest Pfade der Form "messages.xxx" aus lang.yml (ohne "messages."-Prefix). * * Beispiel: formatMessage("messages.ticket-created") → lang "ticket.created" * * @deprecated Direkt {@link #lang()} verwenden. */ @Deprecated public String formatMessage(String path) { // "messages.ticket-created" → "ticket.created" (legacy-Mapping) String langKey = mapLegacyPath(path); if (langKey != null) { return lang().formatWithPrefix(langKey); } // Fallback: Direkt in lang.yml nachschlagen String value = lang().getRaw(path); return lang().getPrefix() + lang().color(value); } /** * Übersetzt &-Farbcodes in §-Farbcodes. * Kompatibilitätsmethode – bevorzugt lang().color() verwenden. */ public String color(String text) { return ChatColor.translateAlternateColorCodes('&', text); } /** * Mappt alte "messages.xxx"-Pfade auf neue lang.yml-Pfade. * Muss ergänzt werden wenn neue Schlüssel im alten Stil genutzt wurden. */ private String mapLegacyPath(String path) { if (path == null) return null; return switch (path) { case "messages.export-success" -> "system.export-success"; case "messages.export-fail" -> "system.export-fail"; case "messages.import-success" -> "system.import-success"; case "messages.import-fail" -> "system.import-fail"; case "messages.migration-success" -> "system.migration-success"; case "messages.migration-fail" -> "system.migration-fail"; case "messages.archive-success" -> "system.archive-success"; case "messages.archive-fail" -> "system.archive-fail"; case "messages.file-not-found" -> "system.file-not-found"; case "messages.unknown-mode" -> "system.unknown-mode"; case "messages.validation-warning" -> "system.validation-warning"; case "messages.ticket-created" -> "ticket.created"; case "messages.ticket-created-category" -> "ticket.created-category"; case "messages.ticket-claimed" -> "ticket.claimed"; case "messages.ticket-claimed-notify" -> "ticket.claimed-notify"; case "messages.ticket-closed" -> "ticket.closed"; case "messages.ticket-closed-notify" -> "ticket.closed-notify"; case "messages.ticket-forwarded" -> "ticket.forwarded"; case "messages.ticket-forwarded-notify" -> "ticket.forwarded-notify"; case "messages.ticket-forwarded-creator-notify" -> "ticket.forwarded-creator"; case "messages.new-ticket-notify" -> "ticket.new-notify"; case "messages.comment-saved" -> "comment.saved"; case "messages.comment-notify" -> "comment.notify-online"; case "messages.comment-no-permission" -> "comment.no-permission"; case "messages.rating-saved-good" -> "rating.saved-good"; case "messages.rating-saved-bad" -> "rating.saved-bad"; case "messages.rating-already-rated" -> "rating.already-rated"; case "messages.rating-not-yours" -> "rating.not-yours"; case "messages.rating-disabled" -> "rating.disabled"; case "messages.rating-prompt" -> "rating.prompt-title"; case "messages.blacklist-added" -> "blacklist.added"; case "messages.blacklist-removed" -> "blacklist.removed"; case "messages.blacklist-already" -> "blacklist.already"; case "messages.blacklist-not-found" -> "blacklist.not-found"; case "messages.blacklist-blocked" -> "create.blacklist-blocked"; case "messages.no-permission" -> "general.no-permission"; case "messages.no-open-tickets" -> "general.no-open-tickets"; case "messages.join-open-tickets" -> "join.open-tickets"; case "messages.already-claimed" -> "general.already-claimed"; case "messages.ticket-not-found" -> "general.ticket-not-found"; case "messages.cooldown" -> "general.cooldown"; case "messages.category-invalid" -> "create.category-invalid"; default -> null; }; } // ─────────────────────────── Getter ──────────────────────────────────── /** * Aktualisiert serverName und debug-Flag aus der (bereits neu geladenen) Config. * Muss nach plugin.reloadConfig() aufgerufen werden. */ public void reloadSettings() { serverName = getConfig().getString("server-name", "unknown"); if ("unknown".equals(serverName)) { getLogger().warning("[BungeeCord] Kein 'server-name' in der config.yml definiert!"); } debug = getConfig().getBoolean("debug", false); TicketPriority.reloadLocalizedNames(this); } public static TicketPlugin getInstance() { return instance; } public LanguageManager getLanguageManager() { return languageManager; } public DatabaseManager getDatabaseManager() { return databaseManager; } public TicketManager getTicketManager() { return ticketManager; } public CategoryManager getCategoryManager() { return categoryManager; } public FaqManager getFaqManager() { return faqManager; } public TicketGUI getTicketGUI() { return ticketGUI; } public FaqGUI getFaqGUI() { return faqGUI; } public DiscordWebhook getDiscordWebhook() { return discordWebhook; } public BungeeMessenger getBungeeMessenger() { return bungeeMessenger; } public TicketCache getTicketCache() { return ticketCache; } public boolean isDebug() { return debug; } public String getServerName() { return serverName; } public boolean isBungeeCordEnabled() { return getConfig().getBoolean("bungeecord", false); } public SessionManager getSessionManager() { return sessionManager; } public WebServer getWebServer() { return webServer; } }