Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e7533a214 | |||
| b7de357e81 | |||
| a91e17a097 | |||
| 12c9379797 | |||
| 535b0aa2f3 | |||
| d14646c5ae | |||
| df6878db2f | |||
| 526cb8b442 | |||
| b930793c50 | |||
| 566941d687 |
325
README.md
325
README.md
@@ -1,219 +1,182 @@
|
||||
<div align="center">
|
||||
# TicketSystem
|
||||
|
||||
# 🎫 TicketSystem
|
||||
  
|
||||
|
||||
### Das flexible, moderne Support- und Feedback-System für Minecraft-Server
|
||||
**TicketSystem** ist das flexible, moderne Support- und Feedback-Plugin für Minecraft-Server. Es bietet flexible Speicherung (MySQL oder Datei), automatische Archivierung, Migration, Export/Import, Statistiken, vollständige Validierung, Debug-Modus, eine übersichtliche config.yml mit Versionsprüfung und eine dynamische GUI.
|
||||
|
||||
[](https://www.spigotmc.org/)
|
||||
[](https://adoptium.net/)
|
||||
[](https://github.com/)
|
||||
[](LICENSE)
|
||||
## Features
|
||||
|
||||
**⭐ Zero-Lag · Production Ready · Fully Customizable ⭐**
|
||||
|
||||
[Features](#-features) · [Installation](#-installation) · [Konfiguration](#-konfiguration) · [Befehle](#-befehle--permissions) · [FAQ](#-faq) · [Support](#-support)
|
||||
|
||||
</div>
|
||||
- **MySQL oder Datei-Speicherung** – YAML/JSON oder MySQL/MariaDB, jederzeit umschaltbar, Migration & Backup inklusive
|
||||
- **Automatische Backups & Migration** – Sicheres Wechseln zwischen Speicherarten, Datenverlust ausgeschlossen
|
||||
- **Export/Import** – Tickets einfach zwischen Servern oder Instanzen übertragen
|
||||
- **Statistiken & Archivierung** – Übersichtliche Auswertung, automatische Archivierung nach Zeitplan, manuelles Archivieren möglich
|
||||
- **Rollenbasierter Archiv-Zugriff** – Nur Spieler mit `ticket.archive` können das Archiv sehen, öffnen und Tickets permanent löschen – unabhängig von `ticket.admin` oder OP-Status
|
||||
- **Konfigurierbare Speicherpfade** – Daten- und Archivdateien frei wählbar, auch absolute Pfade
|
||||
- **Vollständige Validierung** – Fehlerhafte Tickets werden beim Laden erkannt, gemeldet und übersprungen
|
||||
- **Bessere Fehlerausgaben** – Alle Fehler erscheinen im Log und für Admins im Chat, inkl. Validierungs- und Speicherfehler
|
||||
- **Debug-Modus & Versionsprüfung** – Für Entwickler und Admins, erkennt veraltete config.yml automatisch
|
||||
- **Komplett anpassbar** – Nachrichten, Farben, Limits, Speicherpfade, Archiv-Intervall, Cooldowns, Rechte
|
||||
- **Dynamische GUI** – Die Ticket-GUI passt sich automatisch der Ticketanzahl an (bis zu 45 Tickets pro Seite), Item-Material richtet sich nach der konfigurierten Kategorie
|
||||
- **Seiten-System** – Bei sehr vielen Tickets wird automatisch geblättert
|
||||
- **Kategorie-System** – Frei konfigurierbare Kategorien (Name, Farbe, Material, Aliases) in der config.yml
|
||||
- **Prioritäten-System** – Vier Stufen (LOW / NORMAL / HIGH / URGENT), beim Erstellen wählbar und nachträglich via GUI oder Befehl änderbar
|
||||
- **Bewertungs-System** – Spieler können nach Ticket-Schließung den Support bewerten (`good` / `bad`), Ergebnisse in `/ticket stats`
|
||||
- **Kommentar-System** – Spieler und Support können Nachrichten direkt am Ticket hinterlassen
|
||||
- **Offline-Benachrichtigungen** – Verpasste Kommentar-, Schließ- und Status-Benachrichtigungen werden gespeichert und beim nächsten Login angezeigt
|
||||
- **Discord-Webhook** – Benachrichtigungen mit Embeds, konfigurierbarem Rollen-Ping und Kategorie/Priorität-Anzeige
|
||||
- **Blacklist** – Spieler vom Ticket-System ausschließen
|
||||
- **Performance** – Optimiert für große Server, alle Operationen laufen asynchron und ressourcenschonend
|
||||
- **Support & Erweiterbarkeit** – Sauberer Code, viele Hooks für eigene Erweiterungen
|
||||
|
||||
---
|
||||
|
||||
## 📋 Über TicketSystem
|
||||
## Installation & Setup
|
||||
|
||||
**TicketSystem** ist die Komplettlösung für Support, Bug-Reports und Feedback auf deinem Minecraft-Server. Spieler erstellen Tickets direkt im Spiel – Admins verwalten alles komfortabel per GUI oder Befehl. Optimiert für kleine und große Server, vollständig konfigurierbar und vollgepackt mit Profi-Features.
|
||||
1. **TicketSystem.jar** in den `plugins`-Ordner legen und Server starten
|
||||
2. **config.yml** anpassen (Speicherorte, Nachrichten, Limits, Farben, MySQL-Daten etc.)
|
||||
3. **/ticket**-Befehle nutzen (siehe unten)
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
| Feature | Beschreibung |
|
||||
|---|---|
|
||||
| 🗄️ **MySQL & Datei-Speicherung** | YAML/JSON oder MySQL/MariaDB – jederzeit umschaltbar, Migration & Backup inklusive |
|
||||
| 🔄 **Automatische Migration** | Sicheres Wechseln zwischen Speicherarten, Datenverlust ausgeschlossen |
|
||||
| 📤 **Export / Import** | Tickets einfach zwischen Servern oder Instanzen übertragen |
|
||||
| 📊 **Statistiken & Archivierung** | Übersichtliche Auswertung, automatische & manuelle Archivierung nach Zeitplan |
|
||||
| ✅ **Vollständige Validierung** | Fehlerhafte Tickets werden erkannt, gemeldet und übersprungen |
|
||||
| 🐛 **Debug-Modus** | Ausführliche Logs für Entwickler und Admins, erkennt veraltete `config.yml` |
|
||||
| 🖥️ **Dynamische GUI** | Passt sich automatisch der Ticketanzahl an – bis zu 54 Tickets pro Seite mit Blättern |
|
||||
| ⚡ **Performance** | Alle Operationen laufen asynchron – optimiert für große Server |
|
||||
| 🔧 **Komplett anpassbar** | Nachrichten, Farben, Limits, Cooldowns, Rechte – alles in der `config.yml` |
|
||||
| 🧪 **Unit-Tests** | Getestete Speicher-Logik für maximale Zuverlässigkeit |
|
||||
|
||||
---
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
> **Voraussetzungen:** Paper / Spigot / Purpur `1.18.x – 1.21.x` · Java `17+` · optional MySQL/MariaDB
|
||||
|
||||
**Schritt 1 – Plugin installieren**
|
||||
```
|
||||
1. Lade die neueste TicketSystem.jar von den Releases herunter
|
||||
2. Verschiebe die .jar in den /plugins Ordner deines Servers
|
||||
3. Starte den Server neu (kein /reload verwenden!)
|
||||
4. Die Konfigurationsdateien werden automatisch generiert
|
||||
```
|
||||
|
||||
**Schritt 2 – Konfiguration anpassen**
|
||||
```
|
||||
1. Öffne plugins/TicketSystem/config.yml
|
||||
2. Passe Speicherpfade, Nachrichten, Limits und Farben an
|
||||
3. Aktiviere MySQL falls gewünscht und trage Zugangsdaten ein
|
||||
4. Nutze /ticket reload um Änderungen zu übernehmen
|
||||
```
|
||||
|
||||
**Fertig!** Dein Support-System ist einsatzbereit. 🎉
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Konfiguration
|
||||
|
||||
<details>
|
||||
<summary><b>📄 Beispiel: config.yml (klicken zum Ausklappen)</b></summary>
|
||||
|
||||
```yaml
|
||||
# TicketSystem - Hauptkonfiguration
|
||||
# © 2026 Viper Plugins
|
||||
|
||||
version: "2.0"
|
||||
debug: false
|
||||
|
||||
# Speicherung
|
||||
data-file: "data.yml"
|
||||
archive-file: "archive.yml"
|
||||
use-mysql: false
|
||||
use-json: false
|
||||
|
||||
# MySQL (nur wenn use-mysql: true)
|
||||
mysql:
|
||||
host: "localhost"
|
||||
port: 3306
|
||||
database: "tickets"
|
||||
user: "root"
|
||||
password: "password"
|
||||
useSSL: false
|
||||
|
||||
# Archivierung
|
||||
auto-archive-interval-hours: 24 # 0 = deaktiviert
|
||||
|
||||
# Allgemein
|
||||
prefix: "&8[&6Ticket&8] &r"
|
||||
ticket-cooldown: 60 # Sekunden zwischen Tickets
|
||||
max-description-length: 100
|
||||
max-open-tickets-per-player: 2
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 💬 Befehle & Permissions
|
||||
## Befehle & Rechte
|
||||
|
||||
### Spieler-Befehle
|
||||
| Befehl | Beschreibung | Permission |
|
||||
|---|---|---|
|
||||
| `/ticket` | GUI mit allen offenen Tickets öffnen | `ticket.use` |
|
||||
| `/ticket create <Nachricht>` | Neues Ticket erstellen | `ticket.use` |
|
||||
| `/ticket close <ID>` | Eigenes Ticket schließen | `ticket.use` |
|
||||
|
||||
### Admin-Befehle
|
||||
| Befehl | Beschreibung | Permission |
|
||||
|---|---|---|
|
||||
| `/ticket claim <ID>` | Ticket übernehmen | `ticket.admin` |
|
||||
| `/ticket forward <ID> <Spieler>` | Ticket weiterleiten | `ticket.admin` |
|
||||
| `/ticket archive` | Tickets manuell archivieren | `ticket.admin` |
|
||||
| `/ticket export <Datei>` | Tickets exportieren | `ticket.admin` |
|
||||
| `/ticket import <Datei>` | Tickets importieren | `ticket.admin` |
|
||||
| `/ticket migrate <tomysql\|tofile>` | Speicherart migrieren | `ticket.admin` |
|
||||
| `/ticket stats` | Statistiken anzeigen | `ticket.admin` |
|
||||
| `/ticket reload` | Konfiguration neu laden | `ticket.admin` |
|
||||
|
||||
### Permissions-Übersicht
|
||||
```
|
||||
ticket.use → Ticket erstellen und eigene Tickets verwalten (Standard für alle Spieler)
|
||||
ticket.admin → Zugriff auf alle Admin- und Management-Funktionen
|
||||
/ticket - Hilfe & Befehlsübersicht
|
||||
/ticket create [Kategorie] [Priorität] <Text> - Neues Ticket erstellen
|
||||
/ticket list - Eigene Tickets in der GUI anzeigen
|
||||
/ticket comment <ID> <Nachricht> - Kommentar zu einem Ticket hinzufügen
|
||||
/ticket rate <ID> <good|bad> - Abgeschlossenes Ticket bewerten
|
||||
```
|
||||
|
||||
### Support/Admin-Befehle
|
||||
|
||||
```
|
||||
/ticket claim <ID> - Ticket annehmen
|
||||
/ticket close <ID> [Kommentar] - Ticket schließen
|
||||
/ticket forward <ID> <Spieler> - Ticket weiterleiten
|
||||
/ticket setpriority <ID> <low|normal|high|urgent> - Priorität eines Tickets ändern
|
||||
/ticket reload - Konfiguration neu laden (inkl. Kategorien)
|
||||
/ticket stats - Statistiken anzeigen
|
||||
/ticket archive - Geschlossene Tickets archivieren
|
||||
/ticket blacklist <add|remove|list> [Spieler] [Grund] - Blacklist verwalten
|
||||
/ticket migrate <tomysql|tofile> - Speicherart wechseln
|
||||
/ticket export <Dateiname> - Tickets exportieren
|
||||
/ticket import <Dateiname> - Tickets importieren
|
||||
```
|
||||
|
||||
### Permissions
|
||||
|
||||
| Permission | Beschreibung | Standard |
|
||||
|---|---|---|
|
||||
| `ticket.create` | Ticket erstellen | ✅ alle Spieler |
|
||||
| `ticket.support` | Tickets einsehen, claimen, schließen & Priorität ändern | ❌ manuell vergeben |
|
||||
| `ticket.archive` | Archiv öffnen, einsehen & Tickets permanent löschen | ❌ manuell vergeben |
|
||||
| `ticket.admin` | Voller Zugriff inkl. Weiterleitung, Reload & Blacklist (beinhaltet `ticket.support`) | OP |
|
||||
|
||||
> ⚠️ **Wichtig:** `ticket.archive` ist bewusst **nicht** in `ticket.admin` enthalten und wird auch **nicht automatisch an OPs vergeben**. Das Archiv-Recht muss explizit zugewiesen werden:
|
||||
> ```
|
||||
> /lp user <Spielername> permission set ticket.archive true
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Kategorie & Priorität beim Erstellen
|
||||
|
||||
Beim Erstellen eines Tickets können Kategorie und Priorität optional angegeben werden:
|
||||
|
||||
```
|
||||
/ticket create <Text> → Kategorie: Standard, Priorität: NORMAL
|
||||
/ticket create bug <Text> → Kategorie: Bug, Priorität: NORMAL
|
||||
/ticket create high <Text> → Kategorie: Standard, Priorität: HIGH
|
||||
/ticket create bug high <Text> → Kategorie: Bug, Priorität: HIGH
|
||||
/ticket create question urgent <Text> → Kategorie: Frage, Priorität: URGENT
|
||||
```
|
||||
|
||||
Verfügbare Prioritäten: `low`, `normal`, `high`, `urgent` (auch auf Deutsch: `niedrig`, `hoch`, `dringend`)
|
||||
|
||||
Kategorien und ihre Aliases sind frei in der `config.yml` konfigurierbar.
|
||||
|
||||
---
|
||||
|
||||
## Discord-Webhook
|
||||
|
||||
Der integrierte Discord-Webhook unterstützt:
|
||||
|
||||
- **Embeds** mit Kategorie und Priorität als eigene Felder
|
||||
- **Rollen-Ping** (`@role`) pro Nachrichtentyp einzeln konfigurierbar
|
||||
- **Drei Ereignisse:** Neues Ticket, Ticket geschlossen, Ticket weitergeleitet
|
||||
|
||||
Konfiguration in der `config.yml`:
|
||||
|
||||
```yaml
|
||||
discord:
|
||||
enabled: true
|
||||
webhook-url: "https://discord.com/api/webhooks/..."
|
||||
role-ping-id: "123456789012345678" # Discord-Rollen-ID (leer = kein Ping)
|
||||
messages:
|
||||
new-ticket:
|
||||
role-ping: true
|
||||
show-category: true
|
||||
show-priority: true
|
||||
ticket-closed:
|
||||
enabled: true
|
||||
role-ping: false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
## FAQ
|
||||
|
||||
<details>
|
||||
<summary><b>Kann ich zwischen MySQL und Datei-Speicherung wechseln?</b></summary>
|
||||
**Kann ich zwischen MySQL und Datei-Speicherung wechseln?**
|
||||
> Ja! Mit `/ticket migrate tomysql` oder `/ticket migrate tofile` werden alle Daten automatisch migriert.
|
||||
|
||||
Ja! Einfach per `/ticket migrate tomysql` oder `/ticket migrate tofile`. Das Plugin migriert alle Daten automatisch und sicher – kein Datenverlust.
|
||||
</details>
|
||||
**Wie konfiguriere ich eigene Kategorien?**
|
||||
> In der `config.yml` unter `categories:` — Name, Farbe, Material (für die GUI) und Aliases frei wählbar. Änderungen werden mit `/ticket reload` übernommen.
|
||||
|
||||
<details>
|
||||
<summary><b>Wie viele Tickets passen in die GUI?</b></summary>
|
||||
**Was passiert mit Benachrichtigungen wenn ein Spieler offline ist?**
|
||||
> Alle Kommentar-, Schließ- und Status-Benachrichtigungen werden gespeichert und beim nächsten Login gebündelt angezeigt.
|
||||
|
||||
Bis zu 54 Tickets pro Seite. Bei mehr Tickets wird automatisch geblättert.
|
||||
</details>
|
||||
**Wie ändere ich die Priorität eines bestehenden Tickets?**
|
||||
> Als Support/Admin entweder per Befehl `/ticket setpriority <ID> <Priorität>` oder direkt in der Detail-GUI per Klick.
|
||||
|
||||
<details>
|
||||
<summary><b>Werden automatisch Backups erstellt?</b></summary>
|
||||
**Wie aktiviere ich den Debug-Modus?**
|
||||
> Setze `debug: true` in der `config.yml`.
|
||||
|
||||
Ja, bei jedem Speicherwechsel und regelmäßig nach dem konfigurierten Archiv-Intervall.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Wie aktiviere ich den Debug-Modus?</b></summary>
|
||||
|
||||
Setze `debug: true` in der `config.yml` und nutze anschließend `/ticket reload`.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Wie exportiere/importiere ich Tickets?</b></summary>
|
||||
|
||||
Mit `/ticket export <Dateiname>` und `/ticket import <Dateiname>` – ideal für Server-Umzüge oder Testumgebungen.
|
||||
</details>
|
||||
**Wer darf das Ticket-Archiv sehen?**
|
||||
> Nur Spieler mit der Permission `ticket.archive`. Diese wird weder automatisch an OPs noch an Admins vergeben und muss explizit zugewiesen werden.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Vergleich
|
||||
## Vergleich mit anderen Plugins
|
||||
|
||||
| | **TicketSystem** | SimpleTickets | AdvancedTickets |
|
||||
|---|:---:|:---:|:---:|
|
||||
| Speicher-Migration | ✅ Vollständig | ⚠️ Nur manuell | ❌ |
|
||||
| Automatische Backups | ✅ | ⚠️ Teilweise | ❌ |
|
||||
| Dynamische GUI | ✅ Modern | ⚠️ Basic | ❌ |
|
||||
| Archivierung | ✅ Auto & manuell | ⚠️ Nur manuell | ❌ |
|
||||
| Export / Import | ✅ | ❌ | ❌ |
|
||||
| Debug-Modus | ✅ | ❌ | ❌ |
|
||||
| Update-Checker | ✅ | ❌ | ❌ |
|
||||
| Unit-Tests | ✅ | ❌ | ❌ |
|
||||
| Feature | TicketSystem | SimpleTickets | AdvancedTickets |
|
||||
|----------------------------------|:------------:|:-------------:|:---------------:|
|
||||
| Speicher-Migration | ✔️ | ⚠️ | ✖️ |
|
||||
| Automatische Backups | ✔️ | ⚠️ | ✖️ |
|
||||
| GUI mit Kategorie-Materialien | ✔️ | ⚠️ | ✖️ |
|
||||
| Archivierung | ✔️ | ⚠️ | ✖️ |
|
||||
| Rollenbasierter Archiv-Zugriff | ✔️ | ✖️ | ✖️ |
|
||||
| Kategorie-System (konfigurierbar)| ✔️ | ✖️ | ✖️ |
|
||||
| Prioritäten-System | ✔️ | ✖️ | ✖️ |
|
||||
| Offline-Benachrichtigungen | ✔️ | ✖️ | ✖️ |
|
||||
| Discord-Webhook mit Rollen-Ping | ✔️ | ✖️ | ✖️ |
|
||||
| Bewertungs-System | ✔️ | ✖️ | ✖️ |
|
||||
| Update-Checker | ✔️ | ✖️ | ✖️ |
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Support
|
||||
## Support & Community
|
||||
|
||||
<div align="center">
|
||||
- [Discord Support](https://discord.com/invite/FdRs4BRd8D)
|
||||
- [Git Issues](https://git.viper.ipv64.net/M_Viper/TicketSystem/issues)
|
||||
|
||||
Hast du Fragen, einen Bug gefunden oder eine Feature-Idee?
|
||||
|
||||
[](https://discord.com/invite/FdRs4BRd8D)
|
||||
|
||||
**Wir antworten in der Regel innerhalb von 24 Stunden!**
|
||||
|
||||
Bitte öffne für Bug-Reports ein Issue.
|
||||
|
||||
</div>
|
||||
Wir antworten in der Regel innerhalb von 24 Stunden!
|
||||
|
||||
---
|
||||
|
||||
## 📜 Kompatibilität
|
||||
## ⭐ Unterstütze das Projekt
|
||||
|
||||
| Plattform | Version |
|
||||
|---|---|
|
||||
| Paper | ✅ 1.18.x – 1.21.x |
|
||||
| Spigot | ✅ 1.18.x – 1.21.x |
|
||||
| Purpur | ✅ 1.18.x – 1.21.x |
|
||||
| Folia | ❌ Nicht unterstützt |
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**© 2026 Viper Plugins · TicketSystem · Alle Rechte vorbehalten**
|
||||
|
||||
Wenn TicketSystem deinen Server bereichert hat, freuen wir uns über eine Bewertung auf spigotmc!
|
||||
|
||||
</div>
|
||||
Wenn TicketSystem deinen Server bereichert hat, freuen wir uns über eine **5-Sterne Bewertung auf SpigotMC**!
|
||||
Dein Feedback hilft uns, das Plugin weiter zu verbessern.
|
||||
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>de.ticketsystem</groupId>
|
||||
<artifactId>TicketSystem</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<version>1.0.4</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>TicketSystem</name>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
|
||||
package de.ticketsystem;
|
||||
|
||||
import de.ticketsystem.commands.TicketCommand;
|
||||
import de.ticketsystem.database.DatabaseManager;
|
||||
import de.ticketsystem.discord.DiscordWebhook;
|
||||
import de.ticketsystem.gui.TicketGUI;
|
||||
import de.ticketsystem.listeners.PlayerJoinListener;
|
||||
import de.ticketsystem.manager.CategoryManager;
|
||||
import de.ticketsystem.manager.TicketManager;
|
||||
import de.ticketsystem.model.Ticket;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@@ -16,80 +18,89 @@ public class TicketPlugin extends JavaPlugin {
|
||||
private static TicketPlugin instance;
|
||||
|
||||
private boolean debug;
|
||||
private DatabaseManager databaseManager;
|
||||
private TicketManager ticketManager;
|
||||
private TicketGUI ticketGUI;
|
||||
private DatabaseManager databaseManager;
|
||||
private TicketManager ticketManager;
|
||||
private CategoryManager categoryManager;
|
||||
private TicketGUI ticketGUI;
|
||||
private DiscordWebhook discordWebhook;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
|
||||
// Config speichern falls nicht vorhanden
|
||||
saveDefaultConfig();
|
||||
|
||||
// Update-Checker (Spigot Resource-ID anpassen!)
|
||||
int resourceId = 132757;
|
||||
// Ticket-Klasse für YAML-Serialisierung registrieren
|
||||
Ticket.register();
|
||||
|
||||
// Update-Checker
|
||||
int resourceId = 132757;
|
||||
new UpdateChecker(this, resourceId).getVersion(version -> {
|
||||
String current = getDescription().getVersion();
|
||||
if (!current.equals(version)) {
|
||||
String msg = ChatColor.translateAlternateColorCodes('&',
|
||||
"&6[TicketSystem] &eEs ist eine neue Version verfügbar: &a" + version + " &7(aktuell: " + current + ")");
|
||||
getLogger().info("Es ist eine neue Version verfügbar: " + version + " (aktuell: " + current + ")");
|
||||
// Sende Nachricht an alle Admins (online) mit 1 Sekunde Verzögerung
|
||||
getServer().getScheduler().runTaskLater(this, () -> {
|
||||
getServer().getOnlinePlayers().stream()
|
||||
.filter(p -> p.hasPermission("ticket.admin"))
|
||||
.forEach(p -> p.sendMessage(msg));
|
||||
}, 20L); // 20 Ticks = 1 Sekunde
|
||||
}, 20L);
|
||||
} else {
|
||||
getLogger().info("TicketSystem ist aktuell (Version " + current + ")");
|
||||
}
|
||||
});
|
||||
|
||||
// Versionsprüfung
|
||||
String configVersion = getConfig().getString("version", "");
|
||||
String configVersion = getConfig().getString("version", "");
|
||||
String expectedVersion = "2.0";
|
||||
if (!expectedVersion.equals(configVersion)) {
|
||||
getLogger().warning("[WARNUNG] Die Version deiner config.yml (" + configVersion + ") stimmt nicht mit der erwarteten Version (" + expectedVersion + ") überein! Bitte prüfe, ob deine Konfiguration aktuell ist.");
|
||||
getLogger().warning("[WARNUNG] Die Version deiner config.yml (" + configVersion
|
||||
+ ") stimmt nicht mit der erwarteten Version (" + expectedVersion + ") überein!");
|
||||
}
|
||||
|
||||
// Debug-Status aus Config lesen
|
||||
debug = getConfig().getBoolean("debug", false);
|
||||
|
||||
// Datenbankverbindung aufbauen
|
||||
// Datenbankverbindung
|
||||
databaseManager = new DatabaseManager(this);
|
||||
if (!databaseManager.connect()) {
|
||||
getLogger().severe("Konnte keine Datenbankverbindung herstellen! Plugin läuft im Datei-Modus weiter.");
|
||||
if (isDebug()) getLogger().warning("[DEBUG] DatabaseManager.connect() fehlgeschlagen, Datei-Modus aktiviert.");
|
||||
// Plugin bleibt aktiv, DatabaseManager wechselt auf Datei-Storage
|
||||
}
|
||||
|
||||
// Manager und GUI initialisieren
|
||||
ticketManager = new TicketManager(this);
|
||||
ticketGUI = new TicketGUI(this);
|
||||
// Manager, GUI & Discord-Webhook initialisieren
|
||||
categoryManager = new CategoryManager(this);
|
||||
ticketManager = new TicketManager(this);
|
||||
ticketGUI = new TicketGUI(this);
|
||||
discordWebhook = new DiscordWebhook(this);
|
||||
|
||||
// Commands registrieren
|
||||
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 der config.yml eingetragen!");
|
||||
} else {
|
||||
getLogger().info("[DiscordWebhook] Integration aktiv.");
|
||||
}
|
||||
}
|
||||
|
||||
// Commands & Events
|
||||
TicketCommand ticketCommand = new TicketCommand(this);
|
||||
Objects.requireNonNull(getCommand("ticket")).setExecutor(ticketCommand);
|
||||
Objects.requireNonNull(getCommand("ticket")).setTabCompleter(ticketCommand);
|
||||
|
||||
// Events registrieren
|
||||
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(ticketGUI, this);
|
||||
|
||||
// Automatische Archivierung nach Zeitplan (Intervall in Stunden, Standard: 24h)
|
||||
// Automatische Archivierung
|
||||
int archiveIntervalH = getConfig().getInt("auto-archive-interval-hours", 24);
|
||||
if (archiveIntervalH > 0) {
|
||||
long ticks = archiveIntervalH * 60L * 60L * 20L; // Stunden → Ticks
|
||||
long ticks = archiveIntervalH * 60L * 60L * 20L;
|
||||
getServer().getScheduler().runTaskTimer(this, () -> {
|
||||
int archived = databaseManager.archiveClosedTickets();
|
||||
if (archived > 0) {
|
||||
getLogger().info("Automatische Archivierung: " + archived + " Tickets archiviert.");
|
||||
if (isDebug()) getLogger().info("[DEBUG] Archivierung ausgeführt, " + archived + " Tickets verschoben.");
|
||||
}
|
||||
}, ticks, ticks);
|
||||
getLogger().info("Automatische Archivierung alle " + archiveIntervalH + " Stunden aktiviert.");
|
||||
if (isDebug()) getLogger().info("[DEBUG] Archivierungs-Timer gesetzt: alle " + archiveIntervalH + " Stunden.");
|
||||
}
|
||||
|
||||
getLogger().info("TicketSystem erfolgreich gestartet!");
|
||||
@@ -97,39 +108,29 @@ public class TicketPlugin extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (databaseManager != null) {
|
||||
databaseManager.disconnect();
|
||||
}
|
||||
if (databaseManager != null) databaseManager.disconnect();
|
||||
getLogger().info("TicketSystem wurde deaktiviert.");
|
||||
}
|
||||
|
||||
// ─────────────────────────── Hilfsmethoden ─────────────────────────────
|
||||
|
||||
/**
|
||||
* Formatiert eine Nachricht aus der Config mit Prefix und Farben.
|
||||
*/
|
||||
public String formatMessage(String path) {
|
||||
String prefix = color(getConfig().getString("prefix", "&8[&6Ticket&8] &r"));
|
||||
String prefix = color(getConfig().getString("prefix", "&8[&6Ticket&8] &r"));
|
||||
String message = getConfig().getString(path, "&cNachricht nicht gefunden: " + path);
|
||||
return prefix + color(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert Farbcodes (&x → §x).
|
||||
*/
|
||||
public String color(String text) {
|
||||
return ChatColor.translateAlternateColorCodes('&', text);
|
||||
}
|
||||
|
||||
// ─────────────────────────── Getter ────────────────────────────────────
|
||||
|
||||
public static TicketPlugin getInstance() { return instance; }
|
||||
public DatabaseManager getDatabaseManager() { return databaseManager; }
|
||||
public TicketManager getTicketManager() { return ticketManager; }
|
||||
public TicketGUI getTicketGUI() { return ticketGUI; }
|
||||
|
||||
/**
|
||||
* Gibt zurück, ob der Debug-Modus aktiv ist (aus config.yml)
|
||||
*/
|
||||
public boolean isDebug() { return debug; }
|
||||
}
|
||||
public static TicketPlugin getInstance() { return instance; }
|
||||
public DatabaseManager getDatabaseManager() { return databaseManager; }
|
||||
public TicketManager getTicketManager() { return ticketManager; }
|
||||
public CategoryManager getCategoryManager() { return categoryManager; }
|
||||
public TicketGUI getTicketGUI() { return ticketGUI; }
|
||||
public DiscordWebhook getDiscordWebhook() { return discordWebhook; }
|
||||
public boolean isDebug() { return debug; }
|
||||
}
|
||||
@@ -26,7 +26,11 @@ public class UpdateChecker {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
|
||||
try (InputStream is = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId).openStream(); Scanner scann = new Scanner(is)) {
|
||||
if (scann.hasNext()) {
|
||||
consumer.accept(scann.next());
|
||||
String latest = scann.next();
|
||||
plugin.getLogger().info("[UpdateChecker] Spigot-API Rückgabe: '" + latest + "'");
|
||||
consumer.accept(latest);
|
||||
} else {
|
||||
plugin.getLogger().warning("[UpdateChecker] Keine Version von Spigot erhalten!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().info("Unable to check for updates: " + e.getMessage());
|
||||
|
||||
@@ -1,241 +1,170 @@
|
||||
|
||||
package de.ticketsystem.commands;
|
||||
|
||||
import de.ticketsystem.TicketPlugin;
|
||||
import de.ticketsystem.model.Ticket;
|
||||
|
||||
import de.ticketsystem.manager.CategoryManager;
|
||||
import de.ticketsystem.model.ConfigCategory;
|
||||
import de.ticketsystem.model.TicketComment;
|
||||
import de.ticketsystem.model.TicketPriority;
|
||||
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 org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class TicketCommand implements CommandExecutor, TabCompleter {
|
||||
// Platzhalter für Admin-Kommandos
|
||||
private void handleMigrate(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(plugin.color("&cBenutzung: /ticket migrate <tomysql|tofile>"));
|
||||
return;
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int migrated = 0;
|
||||
String mode = args[1].toLowerCase();
|
||||
if (mode.equals("tomysql")) {
|
||||
migrated = plugin.getDatabaseManager().migrateToMySQL();
|
||||
} else if (mode.equals("tofile")) {
|
||||
migrated = plugin.getDatabaseManager().migrateToFile();
|
||||
} else {
|
||||
player.sendMessage(plugin.formatMessage("messages.unknown-mode"));
|
||||
return;
|
||||
}
|
||||
int finalMigrated = migrated;
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (finalMigrated > 0) {
|
||||
player.sendMessage(plugin.formatMessage("messages.migration-success")
|
||||
.replace("{count}", String.valueOf(finalMigrated)));
|
||||
} else {
|
||||
player.sendMessage(plugin.formatMessage("messages.migration-fail"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleExport(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(plugin.color("&cBenutzung: /ticket export <Dateiname>"));
|
||||
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.formatMessage("messages.export-success")
|
||||
.replace("{count}", String.valueOf(count)).replace("{file}", filename));
|
||||
} else {
|
||||
player.sendMessage(plugin.formatMessage("messages.export-fail"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleImport(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(plugin.color("&cBenutzung: /ticket import <Dateiname>"));
|
||||
return;
|
||||
}
|
||||
String filename = args[1];
|
||||
File importFile = new File(plugin.getDataFolder(), filename);
|
||||
if (!importFile.exists()) {
|
||||
player.sendMessage(plugin.formatMessage("messages.file-not-found").replace("{file}", filename));
|
||||
return;
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int count = plugin.getDatabaseManager().importTickets(importFile);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (count > 0) {
|
||||
player.sendMessage(plugin.formatMessage("messages.import-success")
|
||||
.replace("{count}", String.valueOf(count)));
|
||||
} else {
|
||||
player.sendMessage(plugin.formatMessage("messages.import-fail"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleStats(Player player) {
|
||||
if (!player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
var stats = plugin.getDatabaseManager().getTicketStats();
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.sendMessage("§6--- Ticket Statistik ---");
|
||||
player.sendMessage("§eGesamt: §a" + stats.total + " §7| §eOffen: §a" + stats.open + " §7| §eGeschlossen: §a" + stats.closed + " §7| §eWeitergeleitet: §a" + stats.forwarded);
|
||||
player.sendMessage("§6Top Ersteller:");
|
||||
stats.byPlayer.entrySet().stream().sorted((a,b)->b.getValue()-a.getValue()).limit(5).forEach(e ->
|
||||
player.sendMessage("§e" + e.getKey() + ": §a" + e.getValue())
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────── /ticket archive ────────────────────────────
|
||||
private void handleArchive(Player player) {
|
||||
if (!player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int count = plugin.getDatabaseManager().archiveClosedTickets();
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (count > 0) {
|
||||
player.sendMessage(plugin.formatMessage("messages.archive-success")
|
||||
.replace("{count}", String.valueOf(count)));
|
||||
} else {
|
||||
player.sendMessage(plugin.formatMessage("messages.archive-fail"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private final TicketPlugin plugin;
|
||||
|
||||
public TicketCommand(TicketPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
public TicketCommand(TicketPlugin plugin) { this.plugin = plugin; }
|
||||
|
||||
@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("Dieser Befehl kann nur von Spielern ausgeführt werden.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length == 0) {
|
||||
plugin.getTicketManager().sendHelpMessage(player);
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) { plugin.getTicketManager().sendHelpMessage(player); return true; }
|
||||
|
||||
switch (args[0].toLowerCase()) {
|
||||
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 "archive" -> handleArchive(player);
|
||||
default -> plugin.getTicketManager().sendHelpMessage(player);
|
||||
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 "archive" -> handleArchive(player);
|
||||
case "comment" -> handleComment(player, args);
|
||||
case "blacklist" -> handleBlacklist(player, args);
|
||||
case "rate" -> handleRate(player, args);
|
||||
case "setpriority" -> handleSetPriority(player, args);
|
||||
default -> plugin.getTicketManager().sendHelpMessage(player);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Methoden wie handleMigrate, handleCreate, handleList, handleClaim, handleClose, handleForward, handleReload, handleStats müssen auf Klassenebene stehen und dürfen nicht innerhalb von onCommand oder anderen Methoden verschachtelt sein.
|
||||
// Entferne alle verschachtelten Methoden und stelle sicher, dass jede Methode nur einmal und auf Klassenebene existiert.
|
||||
|
||||
// ─────────────────────────── /ticket create ────────────────────────────
|
||||
|
||||
private void handleCreate(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.create")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(plugin.color("&cBenutzung: /ticket create <Beschreibung>"));
|
||||
|
||||
// Blacklist-Check
|
||||
if (plugin.getDatabaseManager().isBlacklisted(player.getUniqueId())) {
|
||||
player.sendMessage(plugin.color("&cDu wurdest vom Ticket-System gesperrt und kannst keine Tickets erstellen."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(plugin.color("&cBenutzung: /ticket create [Kategorie] [Priorität] <Beschreibung>"));
|
||||
if (plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
player.sendMessage(plugin.color("&7Kategorien: &ebug&7, &efrage&7, &ebeschwerde&7, &esonstiges&7, &eallgemein"));
|
||||
}
|
||||
if (plugin.getConfig().getBoolean("priorities-enabled", true)) {
|
||||
player.sendMessage(plugin.color("&7Prioritäten: &alow&7, &enormal&7, &6high&7, &curgent"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Cooldown-Check
|
||||
if (plugin.getTicketManager().hasCooldown(player.getUniqueId())) {
|
||||
long remaining = plugin.getTicketManager().getRemainingCooldown(player.getUniqueId());
|
||||
player.sendMessage(plugin.formatMessage("messages.cooldown")
|
||||
.replace("{seconds}", String.valueOf(remaining)));
|
||||
player.sendMessage(plugin.formatMessage("messages.cooldown").replace("{seconds}", String.valueOf(remaining)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ticket-Limit-Check
|
||||
if (plugin.getTicketManager().hasReachedTicketLimit(player.getUniqueId())) {
|
||||
int max = plugin.getConfig().getInt("max-open-tickets-per-player", 2);
|
||||
player.sendMessage(plugin.color("&cDu hast bereits &e" + max + " &coffene Ticket(s). Bitte warte, bis dein Ticket bearbeitet wurde."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Nachricht zusammenbauen
|
||||
String message = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
|
||||
// Kategorie und Priorität optional parsen
|
||||
CategoryManager cm = plugin.getCategoryManager();
|
||||
ConfigCategory category = cm.getDefault();
|
||||
TicketPriority priority = TicketPriority.NORMAL;
|
||||
int messageStartIndex = 1;
|
||||
|
||||
boolean categoriesOn = plugin.getConfig().getBoolean("categories-enabled", true);
|
||||
boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true);
|
||||
|
||||
if (args.length >= 3) {
|
||||
// args[1]: erst als Kategorie prüfen, dann als Priorität
|
||||
if (categoriesOn) {
|
||||
ConfigCategory parsedCat = cm.resolve(args[1]);
|
||||
if (parsedCat != null) {
|
||||
category = parsedCat;
|
||||
messageStartIndex = 2;
|
||||
// args[2]: Priorität prüfen (nur wenn danach noch Text kommt)
|
||||
if (prioritiesOn && args.length >= 4) {
|
||||
TicketPriority parsedPrio = parsePriority(args[2]);
|
||||
if (parsedPrio != null) {
|
||||
priority = parsedPrio;
|
||||
messageStartIndex = 3;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Keine Kategorie erkannt → args[1] als Priorität prüfen
|
||||
if (prioritiesOn) {
|
||||
TicketPriority parsedPrio = parsePriority(args[1]);
|
||||
if (parsedPrio != null) {
|
||||
priority = parsedPrio;
|
||||
messageStartIndex = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (prioritiesOn) {
|
||||
// Kategorien aus → args[1] direkt als Priorität prüfen
|
||||
TicketPriority parsedPrio = parsePriority(args[1]);
|
||||
if (parsedPrio != null) {
|
||||
priority = parsedPrio;
|
||||
messageStartIndex = 2;
|
||||
}
|
||||
}
|
||||
} else if (args.length == 2) {
|
||||
// Nur ein Argument: könnte Kategorie oder Priorität sein, aber kein Text danach
|
||||
// → einfach als Beschreibung behandeln, nichts parsen
|
||||
}
|
||||
|
||||
String message = String.join(" ", Arrays.copyOfRange(args, messageStartIndex, args.length));
|
||||
int maxLen = plugin.getConfig().getInt("max-description-length", 100);
|
||||
if (message.isEmpty()) {
|
||||
player.sendMessage(plugin.color("&cBitte gib eine Beschreibung für dein Ticket an."));
|
||||
return;
|
||||
}
|
||||
if (message.length() > maxLen) {
|
||||
player.sendMessage(plugin.color("&cDeine Beschreibung ist zu lang! Maximal " + maxLen + " Zeichen."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ticket asynchron in DB speichern
|
||||
final ConfigCategory finalCategory = category;
|
||||
final TicketPriority finalPriority = priority;
|
||||
Ticket ticket = new Ticket(player.getUniqueId(), player.getName(), message, player.getLocation());
|
||||
ticket.setCategoryKey(finalCategory.getKey());
|
||||
ticket.setPriority(finalPriority);
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int id = plugin.getDatabaseManager().createTicket(ticket);
|
||||
if (id == -1) {
|
||||
player.sendMessage(plugin.color("&cFehler beim Erstellen des Tickets! Bitte wende dich an einen Admin."));
|
||||
return;
|
||||
}
|
||||
if (id == -1) { player.sendMessage(plugin.color("&cFehler beim Erstellen des Tickets!")); return; }
|
||||
ticket.setId(id);
|
||||
plugin.getTicketManager().setCooldown(player.getUniqueId());
|
||||
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-created")
|
||||
.replace("{id}", String.valueOf(id)));
|
||||
|
||||
// Team benachrichtigen
|
||||
String catInfo = plugin.getConfig().getBoolean("categories-enabled", true)
|
||||
? " §7[" + finalCategory.getColored() + "§7]" : "";
|
||||
String prioInfo = plugin.getConfig().getBoolean("priorities-enabled", true)
|
||||
? " §7[" + finalPriority.getColored() + "§7]" : "";
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-created").replace("{id}", String.valueOf(id)) + catInfo + prioInfo);
|
||||
plugin.getTicketManager().notifyTeam(ticket);
|
||||
});
|
||||
});
|
||||
@@ -244,58 +173,38 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
||||
// ─────────────────────────── /ticket list ──────────────────────────────
|
||||
|
||||
private void handleList(Player player) {
|
||||
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
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)));
|
||||
}
|
||||
// GUI öffnen (synchron, Datenbankabfrage läuft darin async)
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||
Bukkit.getScheduler().runTask(plugin, () ->
|
||||
plugin.getTicketGUI().openGUI(player)));
|
||||
}
|
||||
|
||||
// ─────────────────────────── /ticket claim ─────────────────────────────
|
||||
|
||||
private void handleClaim(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(plugin.color("&cBenutzung: /ticket claim <ID>"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length < 2) { player.sendMessage(plugin.color("&cBenutzung: /ticket claim <ID>")); return; }
|
||||
int id;
|
||||
try { id = Integer.parseInt(args[1]); }
|
||||
catch (NumberFormatException e) {
|
||||
player.sendMessage(plugin.color("&cUngültige ID!"));
|
||||
return;
|
||||
}
|
||||
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
|
||||
|
||||
final int ticketId = id;
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().claimTicket(
|
||||
ticketId, player.getUniqueId(), player.getName());
|
||||
|
||||
boolean success = plugin.getDatabaseManager().claimTicket(ticketId, player.getUniqueId(), player.getName());
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (!success) {
|
||||
player.sendMessage(plugin.formatMessage("messages.already-claimed"));
|
||||
return;
|
||||
}
|
||||
if (!success) { player.sendMessage(plugin.formatMessage("messages.already-claimed")); return; }
|
||||
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
|
||||
if (ticket == null) return;
|
||||
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-claimed")
|
||||
.replace("{id}", String.valueOf(ticketId))
|
||||
.replace("{player}", ticket.getCreatorName()));
|
||||
|
||||
plugin.getTicketManager().notifyCreatorClaimed(ticket);
|
||||
|
||||
// Zur Ticket-Position teleportieren
|
||||
if (ticket.getLocation() != null) {
|
||||
player.teleport(ticket.getLocation());
|
||||
}
|
||||
if (ticket.getLocation() != null) player.teleport(ticket.getLocation());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -304,32 +213,31 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
private void handleClose(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(plugin.color("&cBenutzung: /ticket close <ID>"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length < 2) { player.sendMessage(plugin.color("&cBenutzung: /ticket close <ID> [Kommentar]")); return; }
|
||||
int id;
|
||||
try { id = Integer.parseInt(args[1]); }
|
||||
catch (NumberFormatException e) {
|
||||
player.sendMessage(plugin.color("&cUngültige ID!"));
|
||||
return;
|
||||
}
|
||||
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
|
||||
|
||||
String closeComment = args.length > 2 ? String.join(" ", Arrays.copyOfRange(args, 2, args.length)) : "";
|
||||
final int ticketId = id;
|
||||
final String comment = closeComment;
|
||||
|
||||
final int ticketId = id;
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().closeTicket(ticketId);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (success) {
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-closed")
|
||||
.replace("{id}", String.valueOf(ticketId)));
|
||||
} else {
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-not-found"));
|
||||
}
|
||||
});
|
||||
boolean success = plugin.getDatabaseManager().closeTicket(ticketId, comment);
|
||||
if (success) {
|
||||
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-closed").replace("{id}", String.valueOf(ticketId)));
|
||||
if (ticket != null) {
|
||||
ticket.setCloseComment(comment);
|
||||
plugin.getTicketManager().notifyCreatorClosed(ticket, player.getName());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -337,86 +245,452 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
private void handleForward(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
|
||||
}
|
||||
if (args.length < 3) {
|
||||
player.sendMessage(plugin.color("&cBenutzung: /ticket forward <ID> <Spieler>"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length < 3) { player.sendMessage(plugin.color("&cBenutzung: /ticket forward <ID> <Spieler>")); return; }
|
||||
int id;
|
||||
try { id = Integer.parseInt(args[1]); }
|
||||
catch (NumberFormatException e) {
|
||||
player.sendMessage(plugin.color("&cUngültige ID!"));
|
||||
return;
|
||||
}
|
||||
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
|
||||
|
||||
Player target = Bukkit.getPlayer(args[2]);
|
||||
if (target == null || !target.isOnline()) {
|
||||
player.sendMessage(plugin.color("&cSpieler &e" + args[2] + " &cist nicht online!"));
|
||||
if (target == null) { player.sendMessage(plugin.color("&cSpieler nicht gefunden!")); return; }
|
||||
|
||||
final int ticketId = id;
|
||||
final String fromName = player.getName();
|
||||
final Player t = target;
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().forwardTicket(ticketId, t.getUniqueId(), t.getName());
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (!success) { player.sendMessage(plugin.formatMessage("messages.ticket-not-found")); return; }
|
||||
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
|
||||
if (ticket == null) return;
|
||||
player.sendMessage(plugin.color("&aTicket &e#" + ticketId + " &awurde an &e" + t.getName() + " &aweitergeleitet."));
|
||||
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")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
|
||||
}
|
||||
if (args.length < 3) { player.sendMessage(plugin.color("&cBenutzung: /ticket comment <ID> <Nachricht>")); return; }
|
||||
int id;
|
||||
try { id = Integer.parseInt(args[1]); }
|
||||
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
|
||||
|
||||
String msg = String.join(" ", Arrays.copyOfRange(args, 2, args.length));
|
||||
if (msg.length() > 500) { player.sendMessage(plugin.color("&cNachricht zu lang! Maximal 500 Zeichen.")); return; }
|
||||
|
||||
final int ticketId = id;
|
||||
final String message = msg;
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
|
||||
if (ticket == null) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
|
||||
return;
|
||||
}
|
||||
// Spieler darf nur auf eigene Tickets kommentieren (Supporter/Admin: alle)
|
||||
boolean isOwner = ticket.getCreatorUUID().equals(player.getUniqueId());
|
||||
boolean isStaff = player.hasPermission("ticket.support") || player.hasPermission("ticket.admin");
|
||||
if (!isOwner && !isStaff) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.color("&cDu kannst nur deine eigenen Tickets kommentieren.")));
|
||||
return;
|
||||
}
|
||||
|
||||
TicketComment comment = new TicketComment(ticketId, player.getUniqueId(), player.getName(), message);
|
||||
boolean success = plugin.getDatabaseManager().addComment(comment);
|
||||
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (success) {
|
||||
player.sendMessage(plugin.color("&aDein Kommentar zu Ticket &e#" + ticketId + " &awurde gespeichert."));
|
||||
// Supporter/Admin und Ticket-Ersteller benachrichtigen
|
||||
notifyCommentReceivers(player, ticket, message);
|
||||
} else {
|
||||
player.sendMessage(plugin.color("&cFehler beim Speichern des Kommentars."));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyCommentReceivers(Player author, Ticket ticket, String message) {
|
||||
String onlineMsg = plugin.color("&e[Ticket #" + ticket.getId() + "] &f" + author.getName() + " &7hat kommentiert: &f" + message);
|
||||
String offlineMsg = "&e[Ticket #" + ticket.getId() + "] &f" + author.getName() + " &7hat kommentiert (während du offline warst): &f" + message;
|
||||
|
||||
// Ticket-Ersteller benachrichtigen (wenn nicht der Autor selbst)
|
||||
if (!ticket.getCreatorUUID().equals(author.getUniqueId())) {
|
||||
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
|
||||
if (creator != null && creator.isOnline()) {
|
||||
creator.sendMessage(onlineMsg);
|
||||
} else {
|
||||
// Offline → für nächsten Login speichern
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||
plugin.getDatabaseManager().addPendingNotification(ticket.getCreatorUUID(), offlineMsg));
|
||||
}
|
||||
}
|
||||
|
||||
// Supporter/Admin benachrichtigen (wenn Autor kein Supporter ist)
|
||||
if (!author.hasPermission("ticket.support") && !author.hasPermission("ticket.admin")) {
|
||||
// Claimer des Tickets bevorzugt benachrichtigen
|
||||
UUID 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 claimerOffline = "&e[Ticket #" + ticket.getId() + "] &f" + author.getName() + " &7hat auf dein bearbeitetes Ticket kommentiert (offline): &f" + message;
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||
plugin.getDatabaseManager().addPendingNotification(claimerUUID, claimerOffline));
|
||||
}
|
||||
}
|
||||
// Alle anderen Online-Supporter zusätzlich informieren
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (p.getUniqueId().equals(author.getUniqueId())) continue;
|
||||
if (claimerUUID != null && p.getUniqueId().equals(claimerUUID)) continue; // schon oben
|
||||
if (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)) {
|
||||
player.sendMessage(plugin.color("&cBewertungen sind deaktiviert.")); return;
|
||||
}
|
||||
if (args.length < 3) { player.sendMessage(plugin.color("&cBenutzung: /ticket rate <ID> <good|bad>")); return; }
|
||||
int id;
|
||||
try { id = Integer.parseInt(args[1]); }
|
||||
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
|
||||
|
||||
String ratingArg = args[2].toLowerCase();
|
||||
String rating;
|
||||
if (ratingArg.equals("good") || ratingArg.equals("gut") || ratingArg.equals("thumbsup")) {
|
||||
rating = "THUMBS_UP";
|
||||
} else if (ratingArg.equals("bad") || ratingArg.equals("schlecht") || ratingArg.equals("thumbsdown")) {
|
||||
rating = "THUMBS_DOWN";
|
||||
} else {
|
||||
player.sendMessage(plugin.color("&cUngültige Bewertung! Benutze &egood &coder &ebad&c."));
|
||||
return;
|
||||
}
|
||||
|
||||
final int ticketId = id;
|
||||
final Player finalTarget = target;
|
||||
final int ticketId = id;
|
||||
final String finalRating = rating;
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().forwardTicket(
|
||||
ticketId, finalTarget.getUniqueId(), finalTarget.getName());
|
||||
|
||||
if (success) {
|
||||
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-forwarded")
|
||||
.replace("{id}", String.valueOf(ticketId))
|
||||
.replace("{player}", finalTarget.getName()));
|
||||
if (ticket != null) plugin.getTicketManager().notifyForwardedTo(ticket);
|
||||
});
|
||||
} else {
|
||||
Bukkit.getScheduler().runTask(plugin, () ->
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
|
||||
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
|
||||
if (ticket == null) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
|
||||
return;
|
||||
}
|
||||
if (!ticket.getCreatorUUID().equals(player.getUniqueId())) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.color("&cDu kannst nur deine eigenen Tickets bewerten.")));
|
||||
return;
|
||||
}
|
||||
if (ticket.hasRating()) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.color("&cDu hast dieses Ticket bereits bewertet.")));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean success = plugin.getDatabaseManager().rateTicket(ticketId, finalRating);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (success) {
|
||||
String emoji = "THUMBS_UP".equals(finalRating) ? "§a👍 Positiv" : "§c👎 Negativ";
|
||||
player.sendMessage(plugin.color("&aDanke für deine Bewertung! (" + emoji + "§a)"));
|
||||
} else {
|
||||
player.sendMessage(plugin.color("&cBewertung konnte nicht gespeichert werden. Ticket geschlossen?"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────── /ticket blacklist ─────────────────────────
|
||||
|
||||
private void handleBlacklist(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(plugin.color("&cBenutzung: /ticket blacklist <add|remove|list> [Spieler] [Grund]"));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "add" -> {
|
||||
if (args.length < 3) { player.sendMessage(plugin.color("&cBenutzung: /ticket blacklist add <Spieler> [Grund]")); 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) { player.sendMessage(plugin.color("&cSpieler nicht gefunden.")); return; }
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().addBlacklist(
|
||||
target.getUniqueId(), targetName, reason, player.getName());
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (success)
|
||||
player.sendMessage(plugin.color("&a" + targetName + " &awurde zur Ticket-Blacklist hinzugefügt. &7Grund: &e" + reason));
|
||||
else
|
||||
player.sendMessage(plugin.color("&cSpieler ist bereits auf der Blacklist."));
|
||||
});
|
||||
});
|
||||
}
|
||||
case "remove" -> {
|
||||
if (args.length < 3) { player.sendMessage(plugin.color("&cBenutzung: /ticket blacklist remove <Spieler>")); return; }
|
||||
String targetName = args[2];
|
||||
@SuppressWarnings("deprecation")
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(targetName);
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().removeBlacklist(target.getUniqueId());
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (success) player.sendMessage(plugin.color("&a" + targetName + " &awurde von der Blacklist entfernt."));
|
||||
else player.sendMessage(plugin.color("&cSpieler war nicht auf der Blacklist."));
|
||||
});
|
||||
});
|
||||
}
|
||||
case "list" -> {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
var list = plugin.getDatabaseManager().getBlacklist();
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
player.sendMessage(plugin.color("&6Ticket-Blacklist &7(" + list.size() + " Einträge)"));
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
if (list.isEmpty()) {
|
||||
player.sendMessage(plugin.color("&7Keine gesperrten Spieler."));
|
||||
} else {
|
||||
for (String[] entry : list) {
|
||||
// {uuid, name, reason, bannedBy, bannedAt}
|
||||
player.sendMessage(plugin.color("&e" + entry[1] + " &7– &f" + entry[2]
|
||||
+ " &7(gesperrt von &e" + entry[3] + "&7)"));
|
||||
}
|
||||
}
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
});
|
||||
});
|
||||
}
|
||||
default -> player.sendMessage(plugin.color("&cBenutzung: /ticket blacklist <add|remove|list> [Spieler] [Grund]"));
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────── /ticket reload ────────────────────────────
|
||||
|
||||
private void handleReload(Player player) {
|
||||
if (!player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||
return;
|
||||
}
|
||||
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
|
||||
plugin.reloadConfig();
|
||||
player.sendMessage(plugin.color("&aKonfiguration wurde neu geladen."));
|
||||
plugin.getCategoryManager().reload();
|
||||
player.sendMessage(plugin.color("&aKonfiguration wurde neu geladen. &7(inkl. Kategorien)"));
|
||||
}
|
||||
|
||||
// ─────────────────────────── /ticket archive ───────────────────────────
|
||||
|
||||
private void handleArchive(Player player) {
|
||||
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int count = plugin.getDatabaseManager().archiveClosedTickets();
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (count > 0) player.sendMessage(plugin.formatMessage("messages.archive-success").replace("{count}", String.valueOf(count)));
|
||||
else player.sendMessage(plugin.formatMessage("messages.archive-fail"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────── /ticket stats ─────────────────────────────
|
||||
|
||||
private void handleStats(Player player) {
|
||||
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
var stats = plugin.getDatabaseManager().getTicketStats();
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
player.sendMessage(plugin.color("&6Ticket Statistik"));
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
player.sendMessage(plugin.color("&eGesamt: &a" + stats.total));
|
||||
player.sendMessage(plugin.color("&eOffen: &a" + stats.open));
|
||||
player.sendMessage(plugin.color("&eGeschlossen: &a" + stats.closed));
|
||||
player.sendMessage(plugin.color("&eWeitergeleitet: &a" + stats.forwarded));
|
||||
if (plugin.getConfig().getBoolean("rating-enabled", true)) {
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
player.sendMessage(plugin.color("&6Support-Bewertungen"));
|
||||
player.sendMessage(plugin.color("&a👍 Positiv: &f" + stats.thumbsUp
|
||||
+ " &c👎 Negativ: &f" + stats.thumbsDown));
|
||||
int total = stats.thumbsUp + stats.thumbsDown;
|
||||
if (total > 0) {
|
||||
int percent = (int) Math.round(stats.thumbsUp * 100.0 / total);
|
||||
player.sendMessage(plugin.color("&7Zufriedenheit: &e" + percent + "%"));
|
||||
}
|
||||
}
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
player.sendMessage(plugin.color("&6Top Ersteller:"));
|
||||
stats.byPlayer.entrySet().stream()
|
||||
.sorted((a, b) -> b.getValue() - a.getValue())
|
||||
.limit(5)
|
||||
.forEach(e -> player.sendMessage(plugin.color("&e " + e.getKey() + ": &a" + e.getValue())));
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────── /ticket migrate ───────────────────────────
|
||||
|
||||
private void handleMigrate(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
|
||||
if (args.length < 2) { player.sendMessage(plugin.color("&cBenutzung: /ticket migrate <tomysql|tofile>")); return; }
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int migrated = 0;
|
||||
String mode = args[1].toLowerCase();
|
||||
if (mode.equals("tomysql")) migrated = plugin.getDatabaseManager().migrateToMySQL();
|
||||
else if (mode.equals("tofile")) migrated = plugin.getDatabaseManager().migrateToFile();
|
||||
else { player.sendMessage(plugin.formatMessage("messages.unknown-mode")); return; }
|
||||
int f = migrated;
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (f > 0) player.sendMessage(plugin.formatMessage("messages.migration-success").replace("{count}", String.valueOf(f)));
|
||||
else player.sendMessage(plugin.formatMessage("messages.migration-fail"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────── /ticket export ────────────────────────────
|
||||
|
||||
private void handleExport(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
|
||||
if (args.length < 2) { player.sendMessage(plugin.color("&cBenutzung: /ticket export <Dateiname>")); 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.formatMessage("messages.export-success").replace("{count}", String.valueOf(count)).replace("{file}", filename));
|
||||
else player.sendMessage(plugin.formatMessage("messages.export-fail"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────── /ticket import ────────────────────────────
|
||||
|
||||
private void handleImport(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
|
||||
if (args.length < 2) { player.sendMessage(plugin.color("&cBenutzung: /ticket import <Dateiname>")); return; }
|
||||
String filename = args[1];
|
||||
File importFile = new File(plugin.getDataFolder(), filename);
|
||||
if (!importFile.exists()) { player.sendMessage(plugin.formatMessage("messages.file-not-found").replace("{file}", filename)); return; }
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int count = plugin.getDatabaseManager().importTickets(importFile);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (count > 0) player.sendMessage(plugin.formatMessage("messages.import-success").replace("{count}", String.valueOf(count)));
|
||||
else player.sendMessage(plugin.formatMessage("messages.import-fail"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────── /ticket setpriority ──────────────────────
|
||||
|
||||
private void handleSetPriority(Player player, String[] args) {
|
||||
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
|
||||
}
|
||||
if (!plugin.getConfig().getBoolean("priorities-enabled", true)) {
|
||||
player.sendMessage(plugin.color("&cDas Prioritäten-System ist deaktiviert.")); return;
|
||||
}
|
||||
if (args.length < 3) {
|
||||
player.sendMessage(plugin.color("&cBenutzung: /ticket setpriority <ID> <low|normal|high|urgent>")); return;
|
||||
}
|
||||
int ticketId;
|
||||
try { ticketId = Integer.parseInt(args[1]); }
|
||||
catch (NumberFormatException e) {
|
||||
player.sendMessage(plugin.color("&cUngültige Ticket-ID: &e" + args[1])); return;
|
||||
}
|
||||
TicketPriority priority = parsePriority(args[2]);
|
||||
if (priority == null) {
|
||||
player.sendMessage(plugin.color("&cUngültige Priorität! Gültig: &alow&7, &enormal&7, &6high&7, &curgent")); return;
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().setTicketPriority(ticketId, priority);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (success) {
|
||||
player.sendMessage(plugin.color("&aPriorität von Ticket &e#" + ticketId
|
||||
+ " &awurde auf " + priority.getColored() + " &agesetzt."));
|
||||
} else {
|
||||
player.sendMessage(plugin.color("&cTicket &e#" + ticketId + " &cwurde nicht gefunden."));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Parst Benutzer-Eingaben wie "high", "hoch", "urgent", "dringend" etc. zu TicketPriority.
|
||||
* Gibt null zurück wenn keine Übereinstimmung. */
|
||||
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<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<>();
|
||||
if (!(sender instanceof Player player)) return completions;
|
||||
|
||||
if (args.length == 1) {
|
||||
List<String> subs = new ArrayList<>();
|
||||
subs.add("create");
|
||||
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) {
|
||||
subs.addAll(List.of("list", "claim", "close"));
|
||||
}
|
||||
if (player.hasPermission("ticket.admin")) {
|
||||
subs.addAll(List.of("forward", "reload"));
|
||||
}
|
||||
for (String s : subs) {
|
||||
if (s.startsWith(args[0].toLowerCase())) completions.add(s);
|
||||
}
|
||||
List<String> subs = new ArrayList<>(List.of("create", "list", "comment"));
|
||||
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin"))
|
||||
subs.addAll(List.of("claim", "close"));
|
||||
if (plugin.getConfig().getBoolean("rating-enabled", true)) subs.add("rate");
|
||||
if (player.hasPermission("ticket.admin"))
|
||||
subs.addAll(List.of("forward", "reload", "stats", "archive", "migrate", "export", "import", "blacklist"));
|
||||
if ((player.hasPermission("ticket.support") || player.hasPermission("ticket.admin"))
|
||||
&& plugin.getConfig().getBoolean("priorities-enabled", true))
|
||||
subs.add("setpriority");
|
||||
for (String s : subs) if (s.startsWith(args[0].toLowerCase())) completions.add(s);
|
||||
|
||||
} else if (args.length == 2 && args[0].equalsIgnoreCase("create")
|
||||
&& plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
for (ConfigCategory c : plugin.getCategoryManager().getAll())
|
||||
if (c.getKey().startsWith(args[1].toLowerCase())) completions.add(c.getKey());
|
||||
// auch Priorität direkt ohne Kategorie anbieten
|
||||
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 && args[0].equalsIgnoreCase("create")
|
||||
&& plugin.getConfig().getBoolean("priorities-enabled", true)) {
|
||||
// Priorität nach Kategorie
|
||||
for (String p : List.of("low", "normal", "high", "urgent"))
|
||||
if (p.startsWith(args[2].toLowerCase())) completions.add(p);
|
||||
|
||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("setpriority")) {
|
||||
for (String p : List.of("low", "normal", "high", "urgent"))
|
||||
if (p.startsWith(args[2].toLowerCase())) completions.add(p);
|
||||
|
||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("forward")) {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (p.getName().toLowerCase().startsWith(args[2].toLowerCase()))
|
||||
completions.add(p.getName());
|
||||
}
|
||||
for (Player p : Bukkit.getOnlinePlayers())
|
||||
if (p.getName().toLowerCase().startsWith(args[2].toLowerCase())) completions.add(p.getName());
|
||||
|
||||
} else if (args.length == 2 && args[0].equalsIgnoreCase("blacklist")) {
|
||||
completions.addAll(List.of("add", "remove", "list"));
|
||||
|
||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("blacklist")
|
||||
&& (args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("remove"))) {
|
||||
for (Player p : Bukkit.getOnlinePlayers())
|
||||
if (p.getName().toLowerCase().startsWith(args[2].toLowerCase())) completions.add(p.getName());
|
||||
|
||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("rate")) {
|
||||
completions.addAll(List.of("good", "bad"));
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
272
src/main/java/de/ticketsystem/discord/DiscordWebhook.java
Normal file
272
src/main/java/de/ticketsystem/discord/DiscordWebhook.java
Normal file
@@ -0,0 +1,272 @@
|
||||
package de.ticketsystem.discord;
|
||||
|
||||
import de.ticketsystem.TicketPlugin;
|
||||
import de.ticketsystem.model.ConfigCategory;
|
||||
import de.ticketsystem.model.Ticket;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Sendet Benachrichtigungen an einen Discord-Webhook.
|
||||
* Unterstützt Embeds mit Farbe, Feldern, Timestamp, Kategorie, Priorität und Rollen-Ping.
|
||||
*
|
||||
* Relevante config.yml-Felder:
|
||||
*
|
||||
* discord:
|
||||
* enabled: true
|
||||
* webhook-url: "https://discord.com/api/webhooks/..."
|
||||
* role-ping-id: "" # Rollen-ID für @Ping, leer = kein Ping
|
||||
* messages:
|
||||
* new-ticket:
|
||||
* title: "🎫 Neues Ticket erstellt"
|
||||
* color: "3066993"
|
||||
* footer: "TicketSystem"
|
||||
* show-position: true
|
||||
* show-category: true
|
||||
* show-priority: true
|
||||
* role-ping: true # Ping bei neuem Ticket an/aus
|
||||
* ticket-closed:
|
||||
* enabled: false
|
||||
* title: "🔒 Ticket geschlossen"
|
||||
* color: "15158332"
|
||||
* footer: "TicketSystem"
|
||||
* show-category: true
|
||||
* show-priority: true
|
||||
* role-ping: false
|
||||
* ticket-forwarded:
|
||||
* enabled: false
|
||||
* title: "🔀 Ticket weitergeleitet"
|
||||
* color: "15105570"
|
||||
* footer: "TicketSystem"
|
||||
* show-category: true
|
||||
* show-priority: true
|
||||
* role-ping: false
|
||||
*/
|
||||
public class DiscordWebhook {
|
||||
|
||||
private final TicketPlugin plugin;
|
||||
|
||||
public DiscordWebhook(TicketPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
// ─────────────────────────── Öffentliche Methoden ──────────────────────
|
||||
|
||||
/**
|
||||
* Sendet eine Benachrichtigung wenn ein neues Ticket erstellt wurde.
|
||||
*/
|
||||
public void sendNewTicket(Ticket ticket) {
|
||||
if (!isEnabled()) return;
|
||||
|
||||
String webhookUrl = getWebhookUrl();
|
||||
if (webhookUrl == null) return;
|
||||
|
||||
String title = plugin.getConfig().getString ("discord.messages.new-ticket.title", "🎫 Neues Ticket erstellt");
|
||||
String color = plugin.getConfig().getString ("discord.messages.new-ticket.color", "3066993");
|
||||
String footer = plugin.getConfig().getString ("discord.messages.new-ticket.footer", "TicketSystem");
|
||||
boolean showPos = plugin.getConfig().getBoolean("discord.messages.new-ticket.show-position", true);
|
||||
boolean showCat = plugin.getConfig().getBoolean("discord.messages.new-ticket.show-category", true);
|
||||
boolean showPri = plugin.getConfig().getBoolean("discord.messages.new-ticket.show-priority", true);
|
||||
boolean ping = plugin.getConfig().getBoolean("discord.messages.new-ticket.role-ping", true);
|
||||
|
||||
StringBuilder fields = new StringBuilder();
|
||||
fields.append(field("Spieler", ticket.getCreatorName(), true));
|
||||
fields.append(",").append(field("Ticket ID", "#" + ticket.getId(), true));
|
||||
fields.append(",").append(field("Anliegen", ticket.getMessage(), false));
|
||||
|
||||
if (showCat && plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
ConfigCategory cat = plugin.getCategoryManager().fromKey(ticket.getCategoryKey());
|
||||
fields.append(",").append(field("Kategorie", cat.getName(), true));
|
||||
}
|
||||
if (showPri && plugin.getConfig().getBoolean("priorities-enabled", true)) {
|
||||
fields.append(",").append(field("Priorität", ticket.getPriority().getDisplayName(), true));
|
||||
}
|
||||
if (showPos) {
|
||||
fields.append(",").append(field("Welt", ticket.getWorldName(), true));
|
||||
fields.append(",").append(field("Position",
|
||||
String.format("%.0f, %.0f, %.0f", ticket.getX(), ticket.getY(), ticket.getZ()), true));
|
||||
}
|
||||
|
||||
String content = ping ? buildRolePing() : "";
|
||||
String json = buildPayload(content, title, Integer.parseInt(color), fields.toString(), footer);
|
||||
sendAsync(webhookUrl, json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet eine Benachrichtigung wenn ein Ticket geschlossen wurde.
|
||||
*/
|
||||
public void sendTicketClosed(Ticket ticket, String closerName) {
|
||||
if (!isEnabled()) return;
|
||||
if (!plugin.getConfig().getBoolean("discord.messages.ticket-closed.enabled", false)) return;
|
||||
|
||||
String webhookUrl = getWebhookUrl();
|
||||
if (webhookUrl == null) return;
|
||||
|
||||
String title = plugin.getConfig().getString ("discord.messages.ticket-closed.title", "🔒 Ticket geschlossen");
|
||||
String color = plugin.getConfig().getString ("discord.messages.ticket-closed.color", "15158332");
|
||||
String footer = plugin.getConfig().getString ("discord.messages.ticket-closed.footer", "TicketSystem");
|
||||
boolean showCat = plugin.getConfig().getBoolean("discord.messages.ticket-closed.show-category", true);
|
||||
boolean showPri = plugin.getConfig().getBoolean("discord.messages.ticket-closed.show-priority", true);
|
||||
boolean ping = plugin.getConfig().getBoolean("discord.messages.ticket-closed.role-ping", false);
|
||||
|
||||
StringBuilder fields = new StringBuilder();
|
||||
fields.append(field("Ticket ID", "#" + ticket.getId(), true));
|
||||
fields.append(",").append(field("Ersteller", ticket.getCreatorName(), true));
|
||||
fields.append(",").append(field("Geschlossen von", closerName, true));
|
||||
|
||||
if (showCat && plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
ConfigCategory cat = plugin.getCategoryManager().fromKey(ticket.getCategoryKey());
|
||||
fields.append(",").append(field("Kategorie", cat.getName(), true));
|
||||
}
|
||||
if (showPri && plugin.getConfig().getBoolean("priorities-enabled", true)) {
|
||||
fields.append(",").append(field("Priorität", ticket.getPriority().getDisplayName(), true));
|
||||
}
|
||||
if (ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty()) {
|
||||
fields.append(",").append(field("Kommentar", ticket.getCloseComment(), false));
|
||||
}
|
||||
|
||||
String content = ping ? buildRolePing() : "";
|
||||
String json = buildPayload(content, title, Integer.parseInt(color), fields.toString(), footer);
|
||||
sendAsync(webhookUrl, json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet eine Benachrichtigung wenn ein Ticket weitergeleitet wurde.
|
||||
*/
|
||||
public void sendTicketForwarded(Ticket ticket, String fromName) {
|
||||
if (!isEnabled()) return;
|
||||
if (!plugin.getConfig().getBoolean("discord.messages.ticket-forwarded.enabled", false)) return;
|
||||
|
||||
String webhookUrl = getWebhookUrl();
|
||||
if (webhookUrl == null) return;
|
||||
|
||||
String title = plugin.getConfig().getString ("discord.messages.ticket-forwarded.title", "🔀 Ticket weitergeleitet");
|
||||
String color = plugin.getConfig().getString ("discord.messages.ticket-forwarded.color", "15105570");
|
||||
String footer = plugin.getConfig().getString ("discord.messages.ticket-forwarded.footer", "TicketSystem");
|
||||
boolean showCat = plugin.getConfig().getBoolean("discord.messages.ticket-forwarded.show-category", true);
|
||||
boolean showPri = plugin.getConfig().getBoolean("discord.messages.ticket-forwarded.show-priority", true);
|
||||
boolean ping = plugin.getConfig().getBoolean("discord.messages.ticket-forwarded.role-ping", false);
|
||||
|
||||
StringBuilder fields = new StringBuilder();
|
||||
fields.append(field("Ticket ID", "#" + ticket.getId(), true));
|
||||
fields.append(",").append(field("Ersteller", ticket.getCreatorName(), true));
|
||||
fields.append(",").append(field("Weitergeleitet von", fromName, true));
|
||||
fields.append(",").append(field("Weitergeleitet an", ticket.getForwardedToName(), true));
|
||||
|
||||
if (showCat && plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
ConfigCategory cat = plugin.getCategoryManager().fromKey(ticket.getCategoryKey());
|
||||
fields.append(",").append(field("Kategorie", cat.getName(), true));
|
||||
}
|
||||
if (showPri && plugin.getConfig().getBoolean("priorities-enabled", true)) {
|
||||
fields.append(",").append(field("Priorität", ticket.getPriority().getDisplayName(), true));
|
||||
}
|
||||
|
||||
String content = ping ? buildRolePing() : "";
|
||||
String json = buildPayload(content, title, Integer.parseInt(color), fields.toString(), footer);
|
||||
sendAsync(webhookUrl, json);
|
||||
}
|
||||
|
||||
// ─────────────────────────── Private Hilfsmethoden ─────────────────────
|
||||
|
||||
private boolean isEnabled() {
|
||||
return plugin.getConfig().getBoolean("discord.enabled", false);
|
||||
}
|
||||
|
||||
/** Gibt die Webhook-URL zurück oder null wenn nicht gesetzt. */
|
||||
private String getWebhookUrl() {
|
||||
String url = plugin.getConfig().getString("discord.webhook-url", "");
|
||||
return url.isEmpty() ? null : url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut den @Rollen-Ping-String aus der konfigurierten Rollen-ID.
|
||||
* Leer wenn keine ID gesetzt.
|
||||
*/
|
||||
private String buildRolePing() {
|
||||
String roleId = plugin.getConfig().getString("discord.role-ping-id", "").trim();
|
||||
if (roleId.isEmpty()) return "";
|
||||
return "<@&" + roleId + ">";
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut einen einzelnen Embed-Field als JSON-String.
|
||||
*/
|
||||
private String field(String name, String value, boolean inline) {
|
||||
String safeValue = value != null
|
||||
? value.replace("\\", "\\\\").replace("\"", "\\\"")
|
||||
: "–";
|
||||
String safeName = name.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
return String.format("{\"name\":\"%s\",\"value\":\"%s\",\"inline\":%b}",
|
||||
safeName, safeValue, inline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut den kompletten Webhook-Payload als JSON.
|
||||
* content = optionaler Ping-Text außerhalb des Embeds.
|
||||
*/
|
||||
private String buildPayload(String content, String title, int color, String fieldsJson, String footer) {
|
||||
String timestamp = Instant.now().toString();
|
||||
String safeTitle = title.replace("\"", "\\\"");
|
||||
String safeFooter = footer.replace("\"", "\\\"");
|
||||
String safeContent = content.replace("\"", "\\\"");
|
||||
|
||||
return String.format("""
|
||||
{
|
||||
"content": "%s",
|
||||
"embeds": [{
|
||||
"title": "%s",
|
||||
"color": %d,
|
||||
"fields": [%s],
|
||||
"footer": { "text": "%s" },
|
||||
"timestamp": "%s"
|
||||
}]
|
||||
}""",
|
||||
safeContent,
|
||||
safeTitle,
|
||||
color,
|
||||
fieldsJson,
|
||||
safeFooter,
|
||||
timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet den JSON-Payload asynchron an den Webhook.
|
||||
*/
|
||||
private void sendAsync(String webhookUrl, String json) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try {
|
||||
URL url = new URL(webhookUrl);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("User-Agent", "TicketSystem-Plugin");
|
||||
conn.setDoOutput(true);
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(5000);
|
||||
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(json.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getLogger().info("[DEBUG] Discord Webhook Response: " + responseCode);
|
||||
}
|
||||
|
||||
if (responseCode != 200 && responseCode != 204) {
|
||||
plugin.getLogger().warning("[DiscordWebhook] Unerwarteter Response-Code: " + responseCode);
|
||||
}
|
||||
|
||||
conn.disconnect();
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[DiscordWebhook] Fehler beim Senden: " + e.getMessage());
|
||||
if (plugin.isDebug()) e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,182 +2,421 @@ package de.ticketsystem.gui;
|
||||
|
||||
import de.ticketsystem.TicketPlugin;
|
||||
import de.ticketsystem.model.Ticket;
|
||||
import de.ticketsystem.manager.CategoryManager;
|
||||
import de.ticketsystem.model.ConfigCategory;
|
||||
import de.ticketsystem.model.TicketComment;
|
||||
import de.ticketsystem.model.TicketPriority;
|
||||
import de.ticketsystem.model.TicketStatus;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
public class TicketGUI implements Listener {
|
||||
|
||||
private static final String GUI_TITLE = "§8§lTicket-Übersicht";
|
||||
// ─────────────────────────── Titel-Konstanten ──────────────────────────
|
||||
|
||||
private static final String GUI_TITLE = "§8§lTicket-Übersicht";
|
||||
private static final String CLOSED_GUI_TITLE = "§8§lTicket-Archiv";
|
||||
private static final String PLAYER_GUI_TITLE = "§8§lMeine Tickets";
|
||||
private static final String DETAIL_GUI_TITLE = "§8§lTicket-Details";
|
||||
private static final String ARCHIVE_PERMISSION = "ticket.archive";
|
||||
|
||||
/** Ticket-Slots pro Seite (Reihen 0–4, Slots 0–44) */
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
||||
|
||||
private final TicketPlugin plugin;
|
||||
|
||||
// Speichert welcher Spieler welches Ticket an welchem Slot hat
|
||||
private final Map<UUID, Map<Integer, Ticket>> playerSlotMap = new HashMap<>();
|
||||
// ─────────────────────────── State-Maps ────────────────────────────────
|
||||
|
||||
public TicketGUI(TicketPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
/** Admin-Übersicht: Slot → Ticket */
|
||||
private final Map<UUID, Map<Integer, Ticket>> playerSlotMap = new HashMap<>();
|
||||
/** Admin-Archiv: Slot → Ticket */
|
||||
private final Map<UUID, Map<Integer, Ticket>> playerClosedSlotMap = new HashMap<>();
|
||||
/** Spieler-GUI: Slot → Ticket */
|
||||
private final Map<UUID, Map<Integer, Ticket>> playerOwnSlotMap = new HashMap<>();
|
||||
/** Detail-Ansicht: UUID → Ticket */
|
||||
private final Map<UUID, Ticket> detailTicketMap = new HashMap<>();
|
||||
|
||||
// ─────────────────────────── GUI öffnen ────────────────────────────────
|
||||
/** Aktuelle Seite pro Spieler (Admin, Archiv, Spieler) */
|
||||
private final Map<UUID, Integer> adminPage = new HashMap<>();
|
||||
private final Map<UUID, Integer> archivePage= new HashMap<>();
|
||||
private final Map<UUID, Integer> playerPage = new HashMap<>();
|
||||
|
||||
public void openGUI(Player player) {
|
||||
List<Ticket> tickets = plugin.getDatabaseManager().getTicketsByStatus(
|
||||
/** Kategorie-Filter für die Admin-GUI: null = alle */
|
||||
private final Map<UUID, ConfigCategory> categoryFilter = new HashMap<>();
|
||||
|
||||
/** Wartet auf Chat-Eingabe für Close-Kommentar */
|
||||
private final Map<UUID, Integer> awaitingComment = new HashMap<>();
|
||||
|
||||
/** Aus Archiv heraus in Detail gegangen */
|
||||
private final Set<UUID> viewingFromArchive = new HashSet<>();
|
||||
|
||||
// ─────────────────────────── Konstruktor ───────────────────────────────
|
||||
|
||||
public TicketGUI(TicketPlugin plugin) { this.plugin = plugin; }
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// ADMIN / SUPPORTER GUI (paginiert, mit Kategorie-Filter)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
public void openGUI(Player player) { openGUI(player, adminPage.getOrDefault(player.getUniqueId(), 0)); }
|
||||
|
||||
public void openGUI(Player player, int page) {
|
||||
adminPage.put(player.getUniqueId(), page);
|
||||
|
||||
List<Ticket> all = plugin.getDatabaseManager().getTicketsByStatus(
|
||||
TicketStatus.OPEN, TicketStatus.CLAIMED, TicketStatus.FORWARDED);
|
||||
|
||||
if (tickets.isEmpty()) {
|
||||
player.sendMessage(plugin.formatMessage("messages.no-open-tickets"));
|
||||
return;
|
||||
// Kategorie-Filter anwenden
|
||||
ConfigCategory filter = categoryFilter.getOrDefault(player.getUniqueId(), null);
|
||||
if (filter != null && plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
all.removeIf(t -> !t.getCategoryKey().equals(filter.getKey()));
|
||||
}
|
||||
|
||||
// Inventar-Größe: nächste Vielfaches von 9 (max. 54 Slots)
|
||||
int size = Math.min(54, (int) (Math.ceil(tickets.size() / 9.0) * 9));
|
||||
if (size < 9) size = 9;
|
||||
// Priorität-Sortierung (URGENT → HIGH → NORMAL → LOW)
|
||||
if (plugin.getConfig().getBoolean("priorities-enabled", true)) {
|
||||
all.sort((a, b) -> b.getPriority().ordinal() - a.getPriority().ordinal());
|
||||
}
|
||||
|
||||
Inventory inv = Bukkit.createInventory(null, size, GUI_TITLE);
|
||||
int totalPages = Math.max(1, (int) Math.ceil((double) all.size() / PAGE_SIZE));
|
||||
page = Math.max(0, Math.min(page, totalPages - 1));
|
||||
adminPage.put(player.getUniqueId(), page);
|
||||
|
||||
Inventory inv = Bukkit.createInventory(null, 54, GUI_TITLE);
|
||||
Map<Integer, Ticket> slotMap = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < tickets.size() && i < 54; i++) {
|
||||
Ticket ticket = tickets.get(i);
|
||||
ItemStack item = buildTicketItem(ticket);
|
||||
inv.setItem(i, item);
|
||||
int start = page * PAGE_SIZE;
|
||||
for (int i = 0; i < PAGE_SIZE && (start + i) < all.size(); i++) {
|
||||
Ticket ticket = all.get(start + i);
|
||||
inv.setItem(i, buildAdminListItem(ticket));
|
||||
slotMap.put(i, ticket);
|
||||
}
|
||||
|
||||
// Trennlinie am Ende, wenn Platz
|
||||
fillEmpty(inv);
|
||||
|
||||
fillAdminNavigation(inv, false, player, page, totalPages);
|
||||
playerSlotMap.put(player.getUniqueId(), slotMap);
|
||||
player.openInventory(inv);
|
||||
}
|
||||
|
||||
// ─────────────────────────── Item bauen ────────────────────────────────
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// ADMIN ARCHIV GUI
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
private ItemStack buildTicketItem(Ticket ticket) {
|
||||
// Material je nach Status
|
||||
Material mat;
|
||||
switch (ticket.getStatus()) {
|
||||
case OPEN -> mat = Material.PAPER;
|
||||
case CLAIMED -> mat = Material.YELLOW_DYE;
|
||||
case FORWARDED -> mat = Material.ORANGE_DYE;
|
||||
default -> mat = Material.PAPER;
|
||||
public void openClosedGUI(Player player) { openClosedGUI(player, archivePage.getOrDefault(player.getUniqueId(), 0)); }
|
||||
|
||||
public void openClosedGUI(Player player, int page) {
|
||||
if (!player.hasPermission(ARCHIVE_PERMISSION)) {
|
||||
player.sendMessage(plugin.color("&cDu hast keine Berechtigung, das Archiv zu öffnen."));
|
||||
return;
|
||||
}
|
||||
archivePage.put(player.getUniqueId(), page);
|
||||
|
||||
List<Ticket> tickets = plugin.getDatabaseManager().getTicketsByStatus(TicketStatus.CLOSED);
|
||||
int totalPages = Math.max(1, (int) Math.ceil((double) tickets.size() / PAGE_SIZE));
|
||||
page = Math.max(0, Math.min(page, totalPages - 1));
|
||||
archivePage.put(player.getUniqueId(), page);
|
||||
|
||||
Inventory inv = Bukkit.createInventory(null, 54, CLOSED_GUI_TITLE);
|
||||
Map<Integer, Ticket> slotMap = new HashMap<>();
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
for (int i = 0; i < PAGE_SIZE && (start + i) < tickets.size(); i++) {
|
||||
Ticket ticket = tickets.get(start + i);
|
||||
inv.setItem(i, buildAdminListItem(ticket));
|
||||
slotMap.put(i, ticket);
|
||||
}
|
||||
|
||||
ItemStack item = new ItemStack(mat);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return item;
|
||||
fillAdminNavigation(inv, true, player, page, totalPages);
|
||||
playerClosedSlotMap.put(player.getUniqueId(), slotMap);
|
||||
player.openInventory(inv);
|
||||
}
|
||||
|
||||
// Display-Name
|
||||
meta.setDisplayName("§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SPIELER-GUI (paginiert)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Lore aufbauen
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("§8§m ");
|
||||
lore.add("§7Ersteller: §e" + ticket.getCreatorName());
|
||||
lore.add("§7Anliegen: §f" + ticket.getMessage());
|
||||
lore.add("§7Erstellt: §e" + DATE_FORMAT.format(ticket.getCreatedAt()));
|
||||
lore.add("§7Welt: §e" + ticket.getWorldName());
|
||||
lore.add(String.format("§7Position: §e%.0f, %.0f, %.0f", ticket.getX(), ticket.getY(), ticket.getZ()));
|
||||
public void openPlayerGUI(Player player) { openPlayerGUI(player, playerPage.getOrDefault(player.getUniqueId(), 0)); }
|
||||
|
||||
if (ticket.getClaimerName() != null) {
|
||||
lore.add("§8§m ");
|
||||
lore.add("§7Geclaimt von: §a" + ticket.getClaimerName());
|
||||
if (ticket.getClaimedAt() != null)
|
||||
lore.add("§7Geclaimt am: §a" + DATE_FORMAT.format(ticket.getClaimedAt()));
|
||||
}
|
||||
if (ticket.getForwardedToName() != null) {
|
||||
lore.add("§7Weitergeleitet an: §6" + ticket.getForwardedToName());
|
||||
public void openPlayerGUI(Player player, int page) {
|
||||
playerPage.put(player.getUniqueId(), page);
|
||||
|
||||
List<Ticket> all = plugin.getDatabaseManager().getTicketsByStatus(
|
||||
TicketStatus.OPEN, TicketStatus.CLAIMED, TicketStatus.FORWARDED, TicketStatus.CLOSED);
|
||||
|
||||
List<Ticket> tickets = new ArrayList<>();
|
||||
for (Ticket t : all) {
|
||||
if (t.getCreatorUUID().equals(player.getUniqueId()) && !t.isPlayerDeleted()) tickets.add(t);
|
||||
}
|
||||
|
||||
lore.add("§8§m ");
|
||||
if (tickets.isEmpty()) {
|
||||
player.sendMessage(plugin.color("&aDu hast aktuell keine Tickets."));
|
||||
return;
|
||||
}
|
||||
|
||||
int totalPages = Math.max(1, (int) Math.ceil((double) tickets.size() / PAGE_SIZE));
|
||||
page = Math.max(0, Math.min(page, totalPages - 1));
|
||||
playerPage.put(player.getUniqueId(), page);
|
||||
|
||||
Inventory inv = Bukkit.createInventory(null, 54, PLAYER_GUI_TITLE);
|
||||
Map<Integer, Ticket> slotMap = new HashMap<>();
|
||||
|
||||
int start = page * PAGE_SIZE;
|
||||
for (int i = 0; i < PAGE_SIZE && (start + i) < tickets.size(); i++) {
|
||||
Ticket ticket = tickets.get(start + i);
|
||||
inv.setItem(i, buildPlayerTicketItem(ticket));
|
||||
slotMap.put(i, ticket);
|
||||
}
|
||||
|
||||
// Nav-Leiste für Spieler-GUI (nur Prev/Next, kein Filter/Archiv)
|
||||
fillPlayerNavigation(inv, page, totalPages);
|
||||
|
||||
playerOwnSlotMap.put(player.getUniqueId(), slotMap);
|
||||
player.openInventory(inv);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// ADMIN DETAIL-GUI
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
public void openDetailGUI(Player player, Ticket ticket) {
|
||||
Inventory inv = Bukkit.createInventory(null, 27, DETAIL_GUI_TITLE);
|
||||
|
||||
// Slot 4: Ticket-Info
|
||||
inv.setItem(4, buildDetailInfoItem(ticket));
|
||||
|
||||
// Slot 10: Teleportieren
|
||||
inv.setItem(10, buildActionItem(Material.ENDER_PEARL, "§b§lTeleportieren",
|
||||
List.of("§7Teleportiert dich zur", "§7Position des Tickets.")));
|
||||
|
||||
// Slot 12: Claimen / Löschen / Grau
|
||||
if (ticket.getStatus() == TicketStatus.OPEN) {
|
||||
lore.add("§a§l» KLICKEN zum Claimen & Teleportieren");
|
||||
inv.setItem(12, buildActionItem(Material.LIME_WOOL, "§a§lTicket annehmen",
|
||||
List.of("§7Nimmt dieses Ticket an", "§7und markiert es als bearbeitet.")));
|
||||
} else if (ticket.getStatus() == TicketStatus.CLOSED && player.hasPermission(ARCHIVE_PERMISSION)) {
|
||||
inv.setItem(12, buildActionItem(Material.BARRIER, "§4§lTicket permanent löschen",
|
||||
List.of("§7Löscht dieses Ticket", "§7unwiderruflich aus der Datenbank.",
|
||||
"§8§m ", "§c§lACHTUNG: §cNicht rückgängig zu machen!")));
|
||||
} else {
|
||||
lore.add("§e§l» KLICKEN zum Teleportieren");
|
||||
inv.setItem(12, buildActionItem(Material.GRAY_WOOL, "§8Bereits angenommen",
|
||||
List.of("§7Dieses Ticket wurde bereits", "§7angenommen.")));
|
||||
}
|
||||
|
||||
meta.setLore(lore);
|
||||
item.setItemMeta(meta);
|
||||
return item;
|
||||
}
|
||||
|
||||
private void fillEmpty(Inventory inv) {
|
||||
ItemStack glass = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||
ItemMeta meta = glass.getItemMeta();
|
||||
if (meta != null) { meta.setDisplayName(" "); glass.setItemMeta(meta); }
|
||||
for (int i = 0; i < inv.getSize(); i++) {
|
||||
if (inv.getItem(i) == null) inv.setItem(i, glass);
|
||||
// Slot 14: Schließen
|
||||
if (ticket.getStatus() != TicketStatus.CLOSED) {
|
||||
inv.setItem(14, buildActionItem(Material.RED_WOOL, "§c§lTicket schließen",
|
||||
List.of("§7Schließt das Ticket.", "§8§m ", "§eKlick für Kommentar-Eingabe.")));
|
||||
} else {
|
||||
inv.setItem(14, buildActionItem(Material.GRAY_WOOL, "§8Bereits geschlossen",
|
||||
List.of("§7Dieses Ticket ist bereits", "§7geschlossen.")));
|
||||
}
|
||||
|
||||
// Slot 22: Kommentare anzeigen
|
||||
inv.setItem(22, buildActionItem(Material.BOOK, "§e§lKommentare anzeigen",
|
||||
List.of("§7Zeigt alle Nachrichten/Antworten", "§7zu diesem Ticket im Chat.")));
|
||||
|
||||
// Slot 20: Priorität ändern (nur wenn priorities-enabled und nicht geschlossen)
|
||||
if (plugin.getConfig().getBoolean("priorities-enabled", true)
|
||||
&& player.hasPermission("ticket.support")
|
||||
&& ticket.getStatus() != TicketStatus.CLOSED) {
|
||||
TicketPriority cur = ticket.getPriority();
|
||||
List<String> prioLore = new ArrayList<>();
|
||||
prioLore.add("§7Aktuell: " + cur.getColored());
|
||||
prioLore.add("§8Klicken zum Wechseln");
|
||||
prioLore.add("§8§m ");
|
||||
for (TicketPriority p : TicketPriority.values())
|
||||
prioLore.add((p == cur ? "§a» " : "§7 ") + p.getColored());
|
||||
inv.setItem(20, buildActionItem(cur.getGuiMaterial(), "§6§lPriorität ändern", prioLore));
|
||||
}
|
||||
|
||||
// Slot 16: Zurück
|
||||
inv.setItem(16, buildActionItem(Material.ARROW, "§7§lZurück",
|
||||
List.of("§7Zurück zur Ticket-Übersicht.")));
|
||||
|
||||
fillEmpty(inv);
|
||||
detailTicketMap.put(player.getUniqueId(), ticket);
|
||||
player.openInventory(inv);
|
||||
}
|
||||
|
||||
// ─────────────────────────── Klick-Event ───────────────────────────────
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// CLICK-EVENTS
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (!(event.getWhoClicked() instanceof Player player)) return;
|
||||
if (!event.getView().getTitle().equals(GUI_TITLE)) return;
|
||||
String title = event.getView().getTitle();
|
||||
|
||||
if (!title.equals(GUI_TITLE) && !title.equals(CLOSED_GUI_TITLE)
|
||||
&& !title.equals(PLAYER_GUI_TITLE) && !title.equals(DETAIL_GUI_TITLE)) return;
|
||||
event.setCancelled(true);
|
||||
|
||||
Map<Integer, Ticket> slotMap = playerSlotMap.get(player.getUniqueId());
|
||||
if (slotMap == null) return;
|
||||
|
||||
int slot = event.getRawSlot();
|
||||
Ticket ticket = slotMap.get(slot);
|
||||
if (ticket == null) return;
|
||||
if (slot < 0) return;
|
||||
|
||||
player.closeInventory();
|
||||
|
||||
// Asynchron aus DB neu laden (aktuelle Daten)
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
Ticket fresh = plugin.getDatabaseManager().getTicketById(ticket.getId());
|
||||
if (fresh == null) {
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-not-found"));
|
||||
return;
|
||||
// ── Admin Haupt-Übersicht ───────────────────────────────────────────
|
||||
if (title.equals(GUI_TITLE)) {
|
||||
handleAdminNavClick(player, slot, false);
|
||||
if (slot < PAGE_SIZE) {
|
||||
Map<Integer, Ticket> slotMap = playerSlotMap.get(player.getUniqueId());
|
||||
if (slotMap == null) return;
|
||||
Ticket ticket = slotMap.get(slot);
|
||||
if (ticket != null) {
|
||||
viewingFromArchive.remove(player.getUniqueId());
|
||||
player.closeInventory();
|
||||
openTicketDetailAsync(player, ticket);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTask(plugin, () -> handleTicketClick(player, fresh));
|
||||
// ── Admin Archiv ───────────────────────────────────────────────────
|
||||
if (title.equals(CLOSED_GUI_TITLE)) {
|
||||
handleArchiveNavClick(player, slot);
|
||||
if (slot < PAGE_SIZE) {
|
||||
Map<Integer, Ticket> slotMap = playerClosedSlotMap.get(player.getUniqueId());
|
||||
if (slotMap == null) return;
|
||||
Ticket ticket = slotMap.get(slot);
|
||||
if (ticket != null) {
|
||||
viewingFromArchive.add(player.getUniqueId());
|
||||
player.closeInventory();
|
||||
openTicketDetailAsync(player, ticket);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Spieler-GUI ────────────────────────────────────────────────────
|
||||
if (title.equals(PLAYER_GUI_TITLE)) {
|
||||
// Navigationstasten
|
||||
int curPage = playerPage.getOrDefault(player.getUniqueId(), 0);
|
||||
if (slot == 45) { openPlayerGUI(player, curPage - 1); return; }
|
||||
if (slot == 53) { openPlayerGUI(player, curPage + 1); return; }
|
||||
|
||||
if (slot < PAGE_SIZE) {
|
||||
Map<Integer, Ticket> slotMap = playerOwnSlotMap.get(player.getUniqueId());
|
||||
if (slotMap == null) return;
|
||||
Ticket ticket = slotMap.get(slot);
|
||||
if (ticket == null) return;
|
||||
|
||||
player.closeInventory();
|
||||
if (ticket.getStatus() == TicketStatus.OPEN || ticket.getStatus() == TicketStatus.CLOSED) {
|
||||
boolean success = plugin.getDatabaseManager().markAsPlayerDeleted(ticket.getId());
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (success) {
|
||||
player.sendMessage(plugin.color("&aDein Ticket &e#" + ticket.getId() + " &awurde aus deiner Übersicht entfernt."));
|
||||
openPlayerGUI(player);
|
||||
} else {
|
||||
player.sendMessage(plugin.color("&cFehler beim Entfernen des Tickets."));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
player.sendMessage(plugin.color("&cDu kannst dieses Ticket nicht löschen, da es bereits von einem Supporter bearbeitet wird."));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Admin Detail-GUI ───────────────────────────────────────────────
|
||||
if (title.equals(DETAIL_GUI_TITLE)) {
|
||||
Ticket ticket = detailTicketMap.get(player.getUniqueId());
|
||||
if (ticket == null) return;
|
||||
player.closeInventory();
|
||||
switch (slot) {
|
||||
case 10 -> handleDetailTeleport(player, ticket);
|
||||
case 12 -> {
|
||||
if (ticket.getStatus() == TicketStatus.CLOSED && player.hasPermission(ARCHIVE_PERMISSION)) {
|
||||
handleDetailPermanentDelete(player, ticket);
|
||||
} else {
|
||||
handleDetailClaim(player, ticket);
|
||||
}
|
||||
}
|
||||
case 14 -> handleDetailClose(player, ticket);
|
||||
case 20 -> handleDetailCyclePriority(player, ticket);
|
||||
case 22 -> handleShowComments(player, ticket);
|
||||
case 16 -> {
|
||||
if (viewingFromArchive.remove(player.getUniqueId())) openClosedGUI(player);
|
||||
else openGUI(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────── Navigation-Handler ─────────────────────────
|
||||
|
||||
/**
|
||||
* Verarbeitet Klicks auf die Navigationsleiste der Admin-Übersicht (Slots 45–53).
|
||||
*/
|
||||
private void handleAdminNavClick(Player player, int slot, boolean isArchive) {
|
||||
int curPage = adminPage.getOrDefault(player.getUniqueId(), 0);
|
||||
switch (slot) {
|
||||
case 45 -> openGUI(player, curPage - 1); // Zurück
|
||||
case 53 -> openGUI(player, curPage + 1); // Vor
|
||||
case 49 -> { // Archiv-Button oder Zurück im Archiv
|
||||
if (player.hasPermission(ARCHIVE_PERMISSION)) openClosedGUI(player);
|
||||
else player.sendMessage(plugin.color("&cDu hast keine Berechtigung, das Archiv zu öffnen."));
|
||||
}
|
||||
case 47 -> { // Kategorie-Filter (wenn aktiviert)
|
||||
if (plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
cycleCategoryFilter(player);
|
||||
openGUI(player, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleArchiveNavClick(Player player, int slot) {
|
||||
int curPage = archivePage.getOrDefault(player.getUniqueId(), 0);
|
||||
switch (slot) {
|
||||
case 45 -> openClosedGUI(player, curPage - 1);
|
||||
case 53 -> openClosedGUI(player, curPage + 1);
|
||||
case 49 -> openGUI(player); // Zurück zur Hauptübersicht
|
||||
}
|
||||
}
|
||||
|
||||
/** Wechselt zum nächsten Kategorie-Filter */
|
||||
private void cycleCategoryFilter(Player player) {
|
||||
CategoryManager cm = plugin.getCategoryManager();
|
||||
List<ConfigCategory> all = cm.getAll();
|
||||
ConfigCategory current = categoryFilter.getOrDefault(player.getUniqueId(), null);
|
||||
|
||||
if (current == null) {
|
||||
if (!all.isEmpty()) categoryFilter.put(player.getUniqueId(), all.get(0));
|
||||
} else {
|
||||
int idx = all.indexOf(current);
|
||||
int next = idx + 1;
|
||||
if (next >= all.size()) categoryFilter.remove(player.getUniqueId()); // Zurück zu Alle
|
||||
else categoryFilter.put(player.getUniqueId(), all.get(next));
|
||||
}
|
||||
ConfigCategory newFilter = categoryFilter.getOrDefault(player.getUniqueId(), null);
|
||||
String filterName = newFilter != null ? newFilter.getColored() : "§7Alle";
|
||||
player.sendMessage(plugin.color("&7Filter: " + filterName));
|
||||
}
|
||||
|
||||
// ─────────────────────────── Detail-Aktionen ────────────────────────────
|
||||
|
||||
private void openTicketDetailAsync(Player player, Ticket currentTicket) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
Ticket fresh = plugin.getDatabaseManager().getTicketById(currentTicket.getId());
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (fresh == null) { player.sendMessage(plugin.formatMessage("messages.ticket-not-found")); return; }
|
||||
openDetailGUI(player, fresh);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleTicketClick(Player player, Ticket ticket) {
|
||||
// Versuche zu claimen, wenn noch OPEN
|
||||
if (ticket.getStatus() == TicketStatus.OPEN) {
|
||||
boolean success = plugin.getDatabaseManager().claimTicket(
|
||||
ticket.getId(), player.getUniqueId(), player.getName());
|
||||
|
||||
if (success) {
|
||||
ticket.setStatus(TicketStatus.CLAIMED);
|
||||
ticket.setClaimerUUID(player.getUniqueId());
|
||||
ticket.setClaimerName(player.getName());
|
||||
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-claimed")
|
||||
.replace("{id}", String.valueOf(ticket.getId()))
|
||||
.replace("{player}", ticket.getCreatorName()));
|
||||
|
||||
plugin.getTicketManager().notifyCreatorClaimed(ticket);
|
||||
} else {
|
||||
player.sendMessage(plugin.formatMessage("messages.already-claimed"));
|
||||
}
|
||||
}
|
||||
|
||||
// Teleportation zur Ticket-Position
|
||||
private void handleDetailTeleport(Player player, Ticket ticket) {
|
||||
if (ticket.getLocation() != null) {
|
||||
player.teleport(ticket.getLocation());
|
||||
player.sendMessage(plugin.color("&7Du wurdest zu Ticket &e#" + ticket.getId() + " &7teleportiert."));
|
||||
@@ -185,4 +424,368 @@ public class TicketGUI implements Listener {
|
||||
player.sendMessage(plugin.color("&cDie Welt des Tickets ist nicht geladen!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDetailClaim(Player player, Ticket ticket) {
|
||||
if (ticket.getStatus() != TicketStatus.OPEN) {
|
||||
player.sendMessage(plugin.formatMessage("messages.already-claimed"));
|
||||
return;
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().claimTicket(ticket.getId(), player.getUniqueId(), player.getName());
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (!success) { player.sendMessage(plugin.formatMessage("messages.already-claimed")); return; }
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-claimed")
|
||||
.replace("{id}", String.valueOf(ticket.getId()))
|
||||
.replace("{player}", ticket.getCreatorName()));
|
||||
ticket.setClaimerUUID(player.getUniqueId());
|
||||
ticket.setClaimerName(player.getName());
|
||||
plugin.getTicketManager().notifyCreatorClaimed(ticket);
|
||||
if (ticket.getLocation() != null) player.teleport(ticket.getLocation());
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
Ticket fresh = plugin.getDatabaseManager().getTicketById(ticket.getId());
|
||||
Bukkit.getScheduler().runTask(plugin, () -> { if (fresh != null) openDetailGUI(player, fresh); });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleDetailPermanentDelete(Player player, Ticket ticket) {
|
||||
if (!player.hasPermission(ARCHIVE_PERMISSION)) {
|
||||
player.sendMessage(plugin.color("&cDu hast keine Berechtigung, Tickets permanent zu löschen."));
|
||||
return;
|
||||
}
|
||||
if (ticket.getStatus() != TicketStatus.CLOSED) {
|
||||
player.sendMessage(plugin.color("&cNur geschlossene Tickets können permanent gelöscht werden."));
|
||||
return;
|
||||
}
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().deleteTicket(ticket.getId());
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (success) {
|
||||
player.sendMessage(plugin.color("&aTicket &e#" + ticket.getId() + " &awurde permanent gelöscht."));
|
||||
viewingFromArchive.remove(player.getUniqueId());
|
||||
openClosedGUI(player);
|
||||
} else {
|
||||
player.sendMessage(plugin.color("&cFehler beim Löschen des Tickets."));
|
||||
openClosedGUI(player);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleDetailClose(Player player, Ticket ticket) {
|
||||
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
||||
player.sendMessage(plugin.color("&cDieses Ticket ist bereits geschlossen."));
|
||||
return;
|
||||
}
|
||||
awaitingComment.put(player.getUniqueId(), ticket.getId());
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
player.sendMessage(plugin.color("&6Ticket #" + ticket.getId() + " schließen"));
|
||||
player.sendMessage(plugin.color("&7Gib einen Kommentar ein (&e- &7für keinen)."));
|
||||
player.sendMessage(plugin.color("&7Abbrechen mit &ccancel"));
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
}
|
||||
|
||||
private void handleDetailCyclePriority(Player player, Ticket ticket) {
|
||||
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.color("&cDu hast keine Berechtigung, die Priorität zu ändern."));
|
||||
return;
|
||||
}
|
||||
if (!plugin.getConfig().getBoolean("priorities-enabled", true)) return;
|
||||
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
||||
player.sendMessage(plugin.color("&cDie Priorität geschlossener Tickets kann nicht geändert werden."));
|
||||
return;
|
||||
}
|
||||
TicketPriority[] values = TicketPriority.values();
|
||||
TicketPriority next = values[(ticket.getPriority().ordinal() + 1) % values.length];
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().setTicketPriority(ticket.getId(), next);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (success) {
|
||||
ticket.setPriority(next);
|
||||
player.sendMessage(plugin.color("&aPriorität auf " + next.getColored() + " &agesetzt."));
|
||||
openDetailGUI(player, ticket);
|
||||
} else {
|
||||
player.sendMessage(plugin.color("&cFehler beim Ändern der Priorität."));
|
||||
openDetailGUI(player, ticket);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void handleShowComments(Player player, Ticket ticket) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
List<TicketComment> comments = plugin.getDatabaseManager().getComments(ticket.getId());
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
player.sendMessage(plugin.color("&6Kommentare zu Ticket #" + ticket.getId()));
|
||||
if (comments.isEmpty()) {
|
||||
player.sendMessage(plugin.color("&7Noch keine Kommentare vorhanden."));
|
||||
} else {
|
||||
for (TicketComment c : comments) {
|
||||
String time = DATE_FORMAT.format(c.getCreatedAt());
|
||||
player.sendMessage(plugin.color("&e" + c.getAuthorName() + " &7(" + time + ")&8: &f" + c.getMessage()));
|
||||
}
|
||||
}
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
// Gleich wieder Detail-GUI öffnen
|
||||
openDetailGUI(player, ticket);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────── Chat-Events ────────────────────────────────
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
if (!awaitingComment.containsKey(player.getUniqueId())) return;
|
||||
event.setCancelled(true);
|
||||
|
||||
int ticketId = awaitingComment.remove(player.getUniqueId());
|
||||
String input = event.getMessage().trim();
|
||||
|
||||
if (input.equalsIgnoreCase("cancel")) {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.color("&cAbgebrochen.")));
|
||||
return;
|
||||
}
|
||||
|
||||
final String comment = input.equals("-") ? "" : input;
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
boolean success = plugin.getDatabaseManager().closeTicket(ticketId, comment);
|
||||
if (success) {
|
||||
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.sendMessage(plugin.formatMessage("messages.ticket-closed").replace("{id}", String.valueOf(ticketId)));
|
||||
if (!comment.isEmpty()) player.sendMessage(plugin.color("&7Kommentar: &f" + comment));
|
||||
if (ticket != null) {
|
||||
ticket.setCloseComment(comment);
|
||||
plugin.getTicketManager().notifyCreatorClosed(ticket, player.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────── Item-Builder ──────────────────────────────
|
||||
|
||||
/**
|
||||
* Füllt die Navigationsleiste (letzte Reihe, Slots 45–53).
|
||||
* Layout: [45]=Zurück | [47]=Filter | [49]=Archiv/Hauptmenü | [51]=leer | [53]=Weiter
|
||||
*/
|
||||
private void fillAdminNavigation(Inventory inv, boolean isArchiveView, Player player, int page, int totalPages) {
|
||||
ItemStack glass = makeGlass();
|
||||
for (int i = 45; i < 54; i++) inv.setItem(i, glass);
|
||||
|
||||
// Zurück (Slot 45)
|
||||
if (page > 0) {
|
||||
inv.setItem(45, buildActionItem(Material.ARROW, "§7§l◄ Zurück",
|
||||
List.of("§7Seite " + page + " von " + totalPages)));
|
||||
}
|
||||
|
||||
// Weiter (Slot 53)
|
||||
if (page < totalPages - 1) {
|
||||
inv.setItem(53, buildActionItem(Material.ARROW, "§7§lWeiter ►",
|
||||
List.of("§7Seite " + (page + 2) + " von " + totalPages)));
|
||||
}
|
||||
|
||||
// Seitenanzeige (Slot 49)
|
||||
if (!isArchiveView) {
|
||||
if (player.hasPermission(ARCHIVE_PERMISSION)) {
|
||||
inv.setItem(49, buildActionItem(Material.CHEST, "§7§lGeschlossene Tickets",
|
||||
List.of("§7Zeigt alle abgeschlossenen", "§7Tickets im Archiv an.")));
|
||||
}
|
||||
// Kategorie-Filter (Slot 47), nur wenn aktiviert
|
||||
if (plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
ConfigCategory currentFilter = categoryFilter.getOrDefault(player.getUniqueId(), null);
|
||||
String filterLabel = currentFilter != null ? currentFilter.getColored() : "§7Alle";
|
||||
List<String> filterLore = new ArrayList<>();
|
||||
filterLore.add("§7Aktuell: " + filterLabel);
|
||||
filterLore.add("§8Klicken zum Wechseln");
|
||||
filterLore.add("§8§m ");
|
||||
for (ConfigCategory cat : plugin.getCategoryManager().getAll()) {
|
||||
filterLore.add((cat.equals(currentFilter) ? "§a» " : "§7 ") + cat.getColored());
|
||||
}
|
||||
filterLore.add((currentFilter == null ? "§a» " : "§7 ") + "§7Alle (kein Filter)");
|
||||
inv.setItem(47, buildActionItem(Material.HOPPER, "§e§lKategorie-Filter", filterLore));
|
||||
}
|
||||
} else {
|
||||
// Im Archiv: Zurück-Button in Slot 49
|
||||
inv.setItem(49, buildActionItem(Material.ARROW, "§7§lZurück zur Übersicht",
|
||||
List.of("§7Zeigt alle offenen Tickets.")));
|
||||
}
|
||||
|
||||
// Seitenanzeige Mitte oben (Slot 48)
|
||||
inv.setItem(48, buildActionItem(Material.PAPER, "§8Seite " + (page + 1) + "/" + totalPages,
|
||||
List.of("§7Gesamt: " + (playerSlotMap.containsKey(player.getUniqueId())
|
||||
? playerSlotMap.get(player.getUniqueId()).size() + "+" : "?") + " Tickets auf dieser Seite")));
|
||||
}
|
||||
|
||||
private void fillPlayerNavigation(Inventory inv, int page, int totalPages) {
|
||||
ItemStack glass = makeGlass();
|
||||
for (int i = 45; i < 54; i++) inv.setItem(i, glass);
|
||||
if (page > 0) inv.setItem(45, buildActionItem(Material.ARROW, "§7§l◄ Zurück", List.of("§7Seite " + page + " von " + totalPages)));
|
||||
if (page < totalPages - 1) inv.setItem(53, buildActionItem(Material.ARROW, "§7§lWeiter ►", List.of("§7Seite " + (page + 2) + " von " + totalPages)));
|
||||
inv.setItem(49, buildActionItem(Material.PAPER, "§8Seite " + (page + 1) + "/" + totalPages, List.of()));
|
||||
}
|
||||
|
||||
// ─────────────────────────── Item-Builder ──────────────────────────────
|
||||
|
||||
private ItemStack buildAdminListItem(Ticket ticket) {
|
||||
// Material: Kategorie aus Config (z.B. REDSTONE für Bug, BOOK für Frage)
|
||||
// Fallback auf Status-Material wenn categories-enabled: false
|
||||
Material mat;
|
||||
if (plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
mat = plugin.getCategoryManager().fromKey(ticket.getCategoryKey()).getMaterial();
|
||||
} else {
|
||||
mat = switch (ticket.getStatus()) {
|
||||
case OPEN -> Material.PAPER;
|
||||
case CLAIMED -> Material.YELLOW_DYE;
|
||||
case FORWARDED -> Material.ORANGE_DYE;
|
||||
case CLOSED -> Material.GRAY_DYE;
|
||||
};
|
||||
}
|
||||
|
||||
ItemStack item = new ItemStack(mat);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return item;
|
||||
|
||||
// Priorität farblich im Titel anzeigen (wenn aktiviert)
|
||||
String priorityPrefix = plugin.getConfig().getBoolean("priorities-enabled", true)
|
||||
? ticket.getPriority().getColored() + " §8| " : "";
|
||||
meta.setDisplayName(priorityPrefix + "§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("§8§m ");
|
||||
lore.add("§7Ersteller: §e" + ticket.getCreatorName());
|
||||
lore.add("§7Anliegen: §f" + ticket.getMessage());
|
||||
lore.add("§7Erstellt: §e" + DATE_FORMAT.format(ticket.getCreatedAt()));
|
||||
if (plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
ConfigCategory _cat = plugin.getCategoryManager().fromKey(ticket.getCategoryKey());
|
||||
lore.add("§7Kategorie: " + _cat.getColored());
|
||||
}
|
||||
if (plugin.getConfig().getBoolean("priorities-enabled", true))
|
||||
lore.add("§7Priorität: " + ticket.getPriority().getColored());
|
||||
if (ticket.getStatus() == TicketStatus.CLOSED && ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty())
|
||||
lore.add("§7Kommentar: §f" + ticket.getCloseComment());
|
||||
if (ticket.isPlayerDeleted()) lore.add("§cSpieler hat Ticket gelöscht.");
|
||||
lore.add("§8§m ");
|
||||
lore.add("§e§l» KLICKEN für Details");
|
||||
meta.setLore(lore);
|
||||
item.setItemMeta(meta);
|
||||
return item;
|
||||
}
|
||||
|
||||
private ItemStack buildDetailInfoItem(Ticket ticket) {
|
||||
Material mat = switch (ticket.getStatus()) {
|
||||
case OPEN -> Material.PAPER;
|
||||
case CLAIMED -> Material.YELLOW_DYE;
|
||||
case FORWARDED -> Material.ORANGE_DYE;
|
||||
case CLOSED -> Material.GRAY_DYE;
|
||||
};
|
||||
ItemStack item = new ItemStack(mat);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return item;
|
||||
|
||||
meta.setDisplayName("§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("§8§m ");
|
||||
lore.add("§7Ersteller: §e" + ticket.getCreatorName());
|
||||
lore.add("§7Anliegen: §f" + ticket.getMessage());
|
||||
lore.add("§7Erstellt: §e" + DATE_FORMAT.format(ticket.getCreatedAt()));
|
||||
lore.add("§7Welt: §e" + ticket.getWorldName());
|
||||
lore.add(String.format("§7Position: §e%.0f, %.0f, %.0f", ticket.getX(), ticket.getY(), ticket.getZ()));
|
||||
if (plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
ConfigCategory _cat = plugin.getCategoryManager().fromKey(ticket.getCategoryKey());
|
||||
lore.add("§7Kategorie: " + _cat.getColored());
|
||||
}
|
||||
if (plugin.getConfig().getBoolean("priorities-enabled", true))
|
||||
lore.add("§7Priorität: " + ticket.getPriority().getColored());
|
||||
if (ticket.getClaimerName() != null) {
|
||||
lore.add("§8§m ");
|
||||
lore.add("§7Angenommen von: §a" + ticket.getClaimerName());
|
||||
if (ticket.getClaimedAt() != null) lore.add("§7Angenommen am: §a" + DATE_FORMAT.format(ticket.getClaimedAt()));
|
||||
}
|
||||
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
||||
if (ticket.getClosedAt() != null) lore.add("§7Geschlossen am: §c" + DATE_FORMAT.format(ticket.getClosedAt()));
|
||||
if (ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty())
|
||||
lore.add("§7Kommentar: §f" + ticket.getCloseComment());
|
||||
if (plugin.getConfig().getBoolean("rating-enabled", true)) {
|
||||
String rating = ticket.getPlayerRating();
|
||||
String ratingStr = rating == null ? "§7Keine Bewertung" :
|
||||
"THUMBS_UP".equals(rating) ? "§a👍 Positiv" : "§c👎 Negativ";
|
||||
lore.add("§7Bewertung: " + ratingStr);
|
||||
}
|
||||
}
|
||||
lore.add("§8§m ");
|
||||
meta.setLore(lore);
|
||||
item.setItemMeta(meta);
|
||||
return item;
|
||||
}
|
||||
|
||||
private ItemStack buildPlayerTicketItem(Ticket ticket) {
|
||||
Material mat = switch (ticket.getStatus()) {
|
||||
case OPEN -> Material.PAPER;
|
||||
case CLAIMED -> Material.YELLOW_DYE;
|
||||
case FORWARDED -> Material.ORANGE_DYE;
|
||||
case CLOSED -> Material.GRAY_DYE;
|
||||
};
|
||||
ItemStack item = new ItemStack(mat);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return item;
|
||||
|
||||
meta.setDisplayName("§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("§8§m ");
|
||||
lore.add("§7Anliegen: §f" + ticket.getMessage());
|
||||
lore.add("§7Erstellt: §e" + DATE_FORMAT.format(ticket.getCreatedAt()));
|
||||
lore.add("§7Welt: §e" + ticket.getWorldName());
|
||||
lore.add(String.format("§7Position: §e%.0f, %.0f, %.0f", ticket.getX(), ticket.getY(), ticket.getZ()));
|
||||
if (plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
ConfigCategory _cat = plugin.getCategoryManager().fromKey(ticket.getCategoryKey());
|
||||
lore.add("§7Kategorie: " + _cat.getColored());
|
||||
}
|
||||
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
||||
if (ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty()) {
|
||||
lore.add("§8§m ");
|
||||
lore.add("§7Kommentar des Supports:");
|
||||
lore.add("§f" + ticket.getCloseComment());
|
||||
}
|
||||
if (plugin.getConfig().getBoolean("rating-enabled", true)) {
|
||||
String rating = ticket.getPlayerRating();
|
||||
if (rating == null) lore.add("§e» /ticket rate " + ticket.getId() + " good/bad");
|
||||
else lore.add("§7Bewertet: " + ("THUMBS_UP".equals(rating) ? "§a👍" : "§c👎"));
|
||||
}
|
||||
}
|
||||
lore.add("§8§m ");
|
||||
switch (ticket.getStatus()) {
|
||||
case OPEN, CLOSED -> { lore.add("§c§l» KLICKEN zum Löschen"); lore.add("§7Entferne dieses Ticket aus deiner Übersicht."); }
|
||||
default -> { lore.add("§e» Ticket wird bearbeitet..."); lore.add("§7Kann nicht mehr gelöscht werden."); }
|
||||
}
|
||||
meta.setLore(lore);
|
||||
item.setItemMeta(meta);
|
||||
return item;
|
||||
}
|
||||
|
||||
private ItemStack buildActionItem(Material material, String displayName, List<String> lore) {
|
||||
ItemStack item = new ItemStack(material);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return item;
|
||||
meta.setDisplayName(displayName);
|
||||
meta.setLore(lore);
|
||||
item.setItemMeta(meta);
|
||||
return item;
|
||||
}
|
||||
|
||||
private ItemStack makeGlass() {
|
||||
ItemStack glass = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||
ItemMeta meta = glass.getItemMeta();
|
||||
if (meta != null) { meta.setDisplayName(" "); glass.setItemMeta(meta); }
|
||||
return glass;
|
||||
}
|
||||
|
||||
private void fillEmpty(Inventory inv) {
|
||||
ItemStack glass = makeGlass();
|
||||
for (int i = 0; i < inv.getSize(); i++) { if (inv.getItem(i) == null) inv.setItem(i, glass); }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package de.ticketsystem.listeners;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.ticketsystem.TicketPlugin;
|
||||
import de.ticketsystem.model.Ticket;
|
||||
import de.ticketsystem.model.TicketStatus;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -19,21 +24,80 @@ public class PlayerJoinListener implements Listener {
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
// Nur Supporter und Admins erhalten die Join-Benachrichtigung
|
||||
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) return;
|
||||
// ── Supporter/Admin: offene Tickets anzeigen ──────────────────────
|
||||
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int count = plugin.getDatabaseManager().countOpenTickets();
|
||||
if (count > 0) {
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
String msg = plugin.formatMessage("messages.join-open-tickets")
|
||||
.replace("{count}", String.valueOf(count));
|
||||
player.sendMessage(msg);
|
||||
player.sendMessage(plugin.color("&7» Tippe &e/ticket list &7für die Übersicht."));
|
||||
}, 40L);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Verzögerung von 2 Sekunden damit die Join-Sequenz abgeschlossen ist
|
||||
// ── Ausstehende Kommentar-/Schließ-Benachrichtigungen anzeigen ────
|
||||
// (Nachrichten die ankamen während der Spieler offline war)
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
if (!player.isOnline()) return;
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
List<String> pending = plugin.getDatabaseManager().getPendingNotifications(player.getUniqueId());
|
||||
if (pending.isEmpty()) return;
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (!player.isOnline()) return;
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
player.sendMessage(plugin.color("&6Ticket-Benachrichtigungen &7(während du offline warst):"));
|
||||
for (String msg : pending) {
|
||||
player.sendMessage(plugin.color(msg));
|
||||
}
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
});
|
||||
plugin.getDatabaseManager().clearPendingNotifications(player.getUniqueId());
|
||||
});
|
||||
}, 60L);
|
||||
|
||||
// ── [NEU] Spieler: Ticket-claimed-Benachrichtigung für Offline-Zeit ──
|
||||
// Läuft mit 60 Ticks Verzögerung (3 Sek) damit der Spieler zuerst normal spawnt
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
if (!player.isOnline()) return;
|
||||
plugin.getTicketManager().notifyClaimedWhileOffline(player);
|
||||
}, 60L);
|
||||
|
||||
// ── Spieler: über geschlossene Tickets mit Kommentar informieren ──
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
int count = plugin.getDatabaseManager().countOpenTickets();
|
||||
List<Ticket> closed = plugin.getDatabaseManager()
|
||||
.getTicketsByStatus(TicketStatus.CLOSED);
|
||||
|
||||
if (count > 0) {
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
String msg = plugin.formatMessage("messages.join-open-tickets")
|
||||
.replace("{count}", String.valueOf(count));
|
||||
player.sendMessage(msg);
|
||||
player.sendMessage(plugin.color("&7» Tippe &e/ticket list &7für die Übersicht."));
|
||||
}, 40L); // 40 Ticks = 2 Sekunden
|
||||
for (Ticket t : closed) {
|
||||
if (!t.getCreatorUUID().equals(player.getUniqueId())) continue;
|
||||
if (t.getCloseComment() == null || t.getCloseComment().isEmpty()) continue;
|
||||
if (plugin.getTicketManager().wasClosedNotificationSent(t.getId())) continue;
|
||||
|
||||
Bukkit.getScheduler().runTask(plugin, () ->
|
||||
plugin.getTicketManager().notifyCreatorClosed(t));
|
||||
}
|
||||
});
|
||||
|
||||
// ── Update-Hinweis für OPs/Admins ────────────────────────────────
|
||||
if (player.isOp() || player.hasPermission("ticket.admin")) {
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
int resourceId = 132757;
|
||||
new de.ticketsystem.UpdateChecker(plugin, resourceId).getVersion(version -> {
|
||||
String current = plugin.getDescription().getVersion();
|
||||
if (!current.equals(version)) {
|
||||
String bar = ChatColor.GOLD + "====================================================";
|
||||
player.sendMessage(bar);
|
||||
player.sendMessage(ChatColor.GOLD + "[TicketSystem] "
|
||||
+ ChatColor.YELLOW + "NEUES UPDATE VERFÜGBAR: v" + version);
|
||||
player.sendMessage(ChatColor.GOLD + "[TicketSystem] "
|
||||
+ ChatColor.YELLOW + "Download: https://www.spigotmc.org/resources/132757");
|
||||
player.sendMessage(bar);
|
||||
}
|
||||
});
|
||||
}, 20L);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
177
src/main/java/de/ticketsystem/manager/CategoryManager.java
Normal file
177
src/main/java/de/ticketsystem/manager/CategoryManager.java
Normal file
@@ -0,0 +1,177 @@
|
||||
package de.ticketsystem.manager;
|
||||
|
||||
import de.ticketsystem.TicketPlugin;
|
||||
import de.ticketsystem.model.ConfigCategory;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Loads and manages ticket categories defined in config.yml under the "categories" section.
|
||||
*
|
||||
* Example config.yml layout:
|
||||
*
|
||||
* categories:
|
||||
* general:
|
||||
* name: "Allgemein"
|
||||
* color: "&7"
|
||||
* material: "PAPER"
|
||||
* aliases:
|
||||
* - "allgemein"
|
||||
* - "general"
|
||||
* bug:
|
||||
* name: "Bug"
|
||||
* color: "&c"
|
||||
* material: "REDSTONE"
|
||||
* aliases:
|
||||
* - "bug"
|
||||
* - "fehler"
|
||||
*/
|
||||
public class CategoryManager {
|
||||
|
||||
private final TicketPlugin plugin;
|
||||
|
||||
/** Ordered map: key → ConfigCategory */
|
||||
private final Map<String, ConfigCategory> categories = new LinkedHashMap<>();
|
||||
|
||||
/** Alias → key mapping for fast resolve() lookups */
|
||||
private final Map<String, String> aliasMap = new LinkedHashMap<>();
|
||||
|
||||
public CategoryManager(TicketPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
load();
|
||||
}
|
||||
|
||||
// ─────────────────────────── Loading ───────────────────────────────────
|
||||
|
||||
private void load() {
|
||||
categories.clear();
|
||||
aliasMap.clear();
|
||||
|
||||
ConfigurationSection section = plugin.getConfig().getConfigurationSection("categories");
|
||||
|
||||
if (section == null || section.getKeys(false).isEmpty()) {
|
||||
// Fallback: create built-in defaults so the plugin always works
|
||||
loadDefaults();
|
||||
return;
|
||||
}
|
||||
|
||||
for (String key : section.getKeys(false)) {
|
||||
ConfigurationSection cat = section.getConfigurationSection(key);
|
||||
if (cat == null) continue;
|
||||
|
||||
String name = cat.getString("name", capitalize(key));
|
||||
String color = cat.getString("color", "&7");
|
||||
String matStr = cat.getString("material", "PAPER").toUpperCase();
|
||||
Material material;
|
||||
try {
|
||||
material = Material.valueOf(matStr);
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getLogger().warning("[CategoryManager] Unbekanntes Material '" + matStr
|
||||
+ "' für Kategorie '" + key + "'. Fallback: PAPER");
|
||||
material = Material.PAPER;
|
||||
}
|
||||
|
||||
ConfigCategory category = new ConfigCategory(key, name, color, material);
|
||||
categories.put(key.toLowerCase(), category);
|
||||
|
||||
// Register key itself as alias
|
||||
aliasMap.put(key.toLowerCase(), key.toLowerCase());
|
||||
|
||||
// Register additional aliases
|
||||
List<String> aliases = cat.getStringList("aliases");
|
||||
for (String alias : aliases) {
|
||||
aliasMap.put(alias.toLowerCase(), key.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
if (categories.isEmpty()) {
|
||||
plugin.getLogger().warning("[CategoryManager] Keine gültigen Kategorien in der config.yml gefunden. Lade Standardkategorien.");
|
||||
loadDefaults();
|
||||
} else {
|
||||
plugin.getLogger().info("[CategoryManager] " + categories.size() + " Kategorie(n) geladen: " + String.join(", ", categories.keySet()));
|
||||
}
|
||||
}
|
||||
|
||||
/** Built-in fallback categories — mirrors the old TicketCategory enum */
|
||||
private void loadDefaults() {
|
||||
addDefault("general", "Allgemein", "&7", Material.PAPER, "allgemein", "general");
|
||||
addDefault("bug", "Bug", "&c", Material.REDSTONE, "bug", "fehler");
|
||||
addDefault("question", "Frage", "&e", Material.BOOK, "frage", "question");
|
||||
addDefault("complaint", "Beschwerde", "&6", Material.WRITABLE_BOOK, "beschwerde", "complaint");
|
||||
addDefault("other", "Sonstiges", "&8", Material.FEATHER, "sonstiges", "other");
|
||||
plugin.getLogger().info("[CategoryManager] Standard-Kategorien geladen (5).");
|
||||
}
|
||||
|
||||
private void addDefault(String key, String name, String color, Material mat, String... aliases) {
|
||||
ConfigCategory cat = new ConfigCategory(key, name, color, mat);
|
||||
categories.put(key, cat);
|
||||
aliasMap.put(key, key);
|
||||
for (String alias : aliases) aliasMap.put(alias.toLowerCase(), key);
|
||||
}
|
||||
|
||||
// ─────────────────────────── Public API ────────────────────────────────
|
||||
|
||||
/**
|
||||
* Returns all loaded categories in config order.
|
||||
*/
|
||||
public List<ConfigCategory> getAll() {
|
||||
return Collections.unmodifiableList(new ArrayList<>(categories.values()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first category (default), or a hard-coded fallback if empty.
|
||||
*/
|
||||
public ConfigCategory getDefault() {
|
||||
if (categories.isEmpty()) return new ConfigCategory("general", "Allgemein", "&7", Material.PAPER);
|
||||
return categories.values().iterator().next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a category by exact key (case-insensitive).
|
||||
* Returns null if not found.
|
||||
*/
|
||||
public ConfigCategory fromKey(String key) {
|
||||
if (key == null) return getDefault();
|
||||
ConfigCategory cat = categories.get(key.toLowerCase());
|
||||
return cat != null ? cat : getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a user-supplied string (key or alias) to a ConfigCategory.
|
||||
* Returns null if no match is found (so callers can show an error).
|
||||
*/
|
||||
public ConfigCategory resolve(String input) {
|
||||
if (input == null) return null;
|
||||
String key = aliasMap.get(input.toLowerCase());
|
||||
return key != null ? categories.get(key) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable comma-separated list of all category keys,
|
||||
* e.g. "general, bug, question, complaint, other"
|
||||
*/
|
||||
public String getAvailableNames() {
|
||||
return String.join(", ", categories.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads categories from the (already reloaded) config.
|
||||
* Call this after plugin.reloadConfig().
|
||||
*/
|
||||
public void reload() {
|
||||
load();
|
||||
}
|
||||
|
||||
// ─────────────────────────── Helpers ───────────────────────────────────
|
||||
|
||||
private static String capitalize(String s) {
|
||||
if (s == null || s.isEmpty()) return s;
|
||||
return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,28 @@
|
||||
package de.ticketsystem.manager;
|
||||
|
||||
import de.ticketsystem.TicketPlugin;
|
||||
import de.ticketsystem.model.ConfigCategory;
|
||||
import de.ticketsystem.model.Ticket;
|
||||
import de.ticketsystem.model.TicketStatus;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class TicketManager {
|
||||
|
||||
private final TicketPlugin plugin;
|
||||
|
||||
// Cooldown Map: UUID → Zeit in Millis, wann das letzte Ticket erstellt wurde
|
||||
/** Cooldown Map: UUID → Zeitstempel letztes Ticket */
|
||||
private final Map<UUID, Long> cooldowns = new HashMap<>();
|
||||
|
||||
/** Ticket-IDs für die der Ersteller bereits über Schließung informiert wurde */
|
||||
private final Set<Integer> notifiedClosedTickets = new HashSet<>();
|
||||
|
||||
public TicketManager(TicketPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
@@ -35,62 +41,182 @@ public class TicketManager {
|
||||
return Math.max(0, (cooldownMillis - elapsed) / 1000);
|
||||
}
|
||||
|
||||
public void setCooldown(UUID uuid) {
|
||||
cooldowns.put(uuid, System.currentTimeMillis());
|
||||
}
|
||||
public void setCooldown(UUID uuid) { cooldowns.put(uuid, System.currentTimeMillis()); }
|
||||
|
||||
// ─────────────────────────── Benachrichtigungen ────────────────────────
|
||||
|
||||
/**
|
||||
* Benachrichtigt alle Online-Supporter und Admins über ein neues Ticket.
|
||||
* Benachrichtigt alle Online-Supporter/Admins über ein neues Ticket
|
||||
* und sendet optional eine Discord-Webhook-Nachricht.
|
||||
*/
|
||||
public void notifyTeam(Ticket ticket) {
|
||||
String creatorName = ticket.getCreatorName() != null ? ticket.getCreatorName() : "Unbekannt";
|
||||
String message = ticket.getMessage() != null ? ticket.getMessage() : "";
|
||||
|
||||
// Kategorie & Priorität optional anzeigen
|
||||
String categoryInfo = "";
|
||||
String priorityInfo = "";
|
||||
if (plugin.getConfig().getBoolean("categories-enabled", true)) {
|
||||
de.ticketsystem.model.ConfigCategory cat = plugin.getCategoryManager().fromKey(ticket.getCategoryKey());
|
||||
categoryInfo = " §7[§r" + cat.getColored() + "§7]";
|
||||
}
|
||||
if (plugin.getConfig().getBoolean("priorities-enabled", true)) {
|
||||
priorityInfo = " §7Priorität: §r" + ticket.getPriority().getColored();
|
||||
}
|
||||
|
||||
String msg = plugin.formatMessage("messages.new-ticket-notify")
|
||||
.replace("{player}", ticket.getCreatorName())
|
||||
.replace("{message}", ticket.getMessage())
|
||||
.replace("{id}", String.valueOf(ticket.getId()));
|
||||
.replace("{player}", creatorName)
|
||||
.replace("{message}", message)
|
||||
.replace("{id}", String.valueOf(ticket.getId()))
|
||||
+ categoryInfo + priorityInfo;
|
||||
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (p.hasPermission("ticket.support") || p.hasPermission("ticket.admin")) {
|
||||
p.sendMessage(msg);
|
||||
|
||||
// Klickbaren Hinweis senden (Bukkit Chat-Component)
|
||||
p.sendMessage(plugin.color("&7» Klicke &e/ticket list &7um die GUI zu öffnen."));
|
||||
}
|
||||
}
|
||||
|
||||
plugin.getDiscordWebhook().sendNewTicket(ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Benachrichtigt den Ersteller des Tickets, wenn es geclaimt wurde.
|
||||
* Benachrichtigt den Ersteller, wenn sein Ticket angenommen wurde.
|
||||
* Setzt claimer_notified = true und persistiert es.
|
||||
*/
|
||||
public void notifyCreatorClaimed(Ticket ticket) {
|
||||
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
|
||||
if (creator != null && creator.isOnline()) {
|
||||
String claimerName = ticket.getClaimerName();
|
||||
if (claimerName == null && ticket.getClaimerUUID() != null)
|
||||
claimerName = Bukkit.getOfflinePlayer(ticket.getClaimerUUID()).getName();
|
||||
if (claimerName == null) claimerName = "Support";
|
||||
|
||||
String msg = plugin.formatMessage("messages.ticket-claimed-notify")
|
||||
.replace("{id}", String.valueOf(ticket.getId()))
|
||||
.replace("{claimer}", ticket.getClaimerName());
|
||||
.replace("{id}", String.valueOf(ticket.getId()))
|
||||
.replace("{claimer}", claimerName);
|
||||
creator.sendMessage(msg);
|
||||
}
|
||||
// Persistiert setzen, damit Join-Listener weiß, dass Spieler bereits informiert ist
|
||||
plugin.getDatabaseManager().markClaimerNotified(ticket.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird beim Server-Join aufgerufen – informiert den Spieler über Tickets,
|
||||
* die geclaimt oder weitergeleitet wurden während er offline war.
|
||||
*/
|
||||
public void notifyClaimedWhileOffline(Player player) {
|
||||
// Suche alle Tickets dieses Spielers, die CLAIMED/FORWARDED sind,
|
||||
// aber noch nicht notified wurden
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
var tickets = plugin.getDatabaseManager().getTicketsByStatus(
|
||||
TicketStatus.CLAIMED, TicketStatus.FORWARDED);
|
||||
|
||||
for (Ticket t : tickets) {
|
||||
if (!t.getCreatorUUID().equals(player.getUniqueId())) continue;
|
||||
if (t.isClaimerNotified()) continue; // wurde schon informiert
|
||||
|
||||
String claimerName = t.getClaimerName() != null ? t.getClaimerName() : "Support";
|
||||
final String name = claimerName;
|
||||
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (!player.isOnline()) return;
|
||||
if (t.getStatus() == TicketStatus.CLAIMED) {
|
||||
String msg = plugin.formatMessage("messages.ticket-claimed-notify")
|
||||
.replace("{id}", String.valueOf(t.getId()))
|
||||
.replace("{claimer}", name);
|
||||
player.sendMessage(msg);
|
||||
} else {
|
||||
String forwardedTo = t.getForwardedToName() != null ? t.getForwardedToName() : "einen Supporter";
|
||||
String msg = plugin.formatMessage("messages.ticket-forwarded-creator-notify")
|
||||
.replace("{id}", String.valueOf(t.getId()))
|
||||
.replace("{supporter}", forwardedTo);
|
||||
player.sendMessage(msg);
|
||||
}
|
||||
});
|
||||
|
||||
plugin.getDatabaseManager().markClaimerNotified(t.getId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Benachrichtigt den Ersteller, wenn sein Ticket weitergeleitet wurde.
|
||||
*/
|
||||
public void notifyCreatorForwarded(Ticket ticket) {
|
||||
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
|
||||
if (creator != null && creator.isOnline()) {
|
||||
String forwardedTo = ticket.getForwardedToName() != null ? ticket.getForwardedToName() : "einen Supporter";
|
||||
String msg = plugin.formatMessage("messages.ticket-forwarded-creator-notify")
|
||||
.replace("{id}", String.valueOf(ticket.getId()))
|
||||
.replace("{supporter}", forwardedTo);
|
||||
creator.sendMessage(msg);
|
||||
}
|
||||
// Auch hier notified setzen
|
||||
plugin.getDatabaseManager().markClaimerNotified(ticket.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet dem weitergeleiteten Supporter eine Benachrichtigung.
|
||||
*/
|
||||
public void notifyForwardedTo(Ticket ticket) {
|
||||
public void notifyForwardedTo(Ticket ticket, String fromName) {
|
||||
Player target = Bukkit.getPlayer(ticket.getForwardedToUUID());
|
||||
if (target != null && target.isOnline()) {
|
||||
String creatorName = ticket.getCreatorName() != null ? ticket.getCreatorName() : "Unbekannt";
|
||||
String msg = plugin.formatMessage("messages.ticket-forwarded-notify")
|
||||
.replace("{player}", ticket.getCreatorName())
|
||||
.replace("{id}", String.valueOf(ticket.getId()));
|
||||
.replace("{player}", creatorName)
|
||||
.replace("{id}", String.valueOf(ticket.getId()));
|
||||
target.sendMessage(msg);
|
||||
}
|
||||
plugin.getDiscordWebhook().sendTicketForwarded(ticket, fromName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Benachrichtigt den Ersteller, wenn sein Ticket geschlossen wurde.
|
||||
*/
|
||||
public void notifyCreatorClosed(Ticket ticket) { notifyCreatorClosed(ticket, null); }
|
||||
|
||||
public void notifyCreatorClosed(Ticket ticket, String closerName) {
|
||||
notifiedClosedTickets.add(ticket.getId());
|
||||
|
||||
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
|
||||
String comment = (ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty())
|
||||
? ticket.getCloseComment() : "";
|
||||
|
||||
if (creator != null && creator.isOnline()) {
|
||||
String msg = plugin.formatMessage("messages.ticket-closed-notify")
|
||||
.replace("{id}", String.valueOf(ticket.getId()))
|
||||
.replace("{comment}", comment);
|
||||
creator.sendMessage(msg);
|
||||
if (!comment.isEmpty())
|
||||
creator.sendMessage(plugin.color("&7Kommentar des Supports: &f" + comment));
|
||||
if (plugin.getConfig().getBoolean("rating-enabled", true)) {
|
||||
creator.sendMessage(plugin.color("&8&m "));
|
||||
creator.sendMessage(plugin.color("&6Wie zufrieden bist du mit dem Support?"));
|
||||
creator.sendMessage(plugin.color("&a/ticket rate " + ticket.getId() + " good &7– 👍 Gut"));
|
||||
creator.sendMessage(plugin.color("&c/ticket rate " + ticket.getId() + " bad &7– 👎 Schlecht"));
|
||||
creator.sendMessage(plugin.color("&8&m "));
|
||||
}
|
||||
} else {
|
||||
// Offline → ausstehende Benachrichtigung speichern
|
||||
String pendingMsg = "&e[Ticket #" + ticket.getId() + "] &7Dein Ticket wurde geschlossen."
|
||||
+ (comment.isEmpty() ? "" : " &7Kommentar: &f" + comment)
|
||||
+ (plugin.getConfig().getBoolean("rating-enabled", true)
|
||||
? " &7Bewertung: &e/ticket rate " + ticket.getId() + " good/bad" : "");
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||
plugin.getDatabaseManager().addPendingNotification(ticket.getCreatorUUID(), pendingMsg));
|
||||
}
|
||||
|
||||
String closer = closerName != null ? closerName : "Unbekannt";
|
||||
plugin.getDiscordWebhook().sendTicketClosed(ticket, closer);
|
||||
}
|
||||
|
||||
public boolean wasClosedNotificationSent(int ticketId) {
|
||||
return notifiedClosedTickets.contains(ticketId);
|
||||
}
|
||||
|
||||
// ─────────────────────────── Hilfsmethoden ─────────────────────────────
|
||||
|
||||
/**
|
||||
* Prüft, ob ein Spieler zu viele offene Tickets hat.
|
||||
*/
|
||||
public boolean hasReachedTicketLimit(UUID uuid) {
|
||||
int max = plugin.getConfig().getInt("max-open-tickets-per-player", 2);
|
||||
if (max <= 0) return false;
|
||||
@@ -101,16 +227,23 @@ public class TicketManager {
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
player.sendMessage(plugin.color("&6TicketSystem &7– Befehle"));
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
player.sendMessage(plugin.color("&e/ticket create <Text> &7– Neues Ticket erstellen"));
|
||||
player.sendMessage(plugin.color("&e/ticket create [Kategorie] <Text> &7– Neues Ticket erstellen"));
|
||||
player.sendMessage(plugin.color("&e/ticket list &7– Deine Tickets ansehen (GUI)"));
|
||||
player.sendMessage(plugin.color("&e/ticket comment <ID> <Text> &7– Nachricht zu einem Ticket"));
|
||||
|
||||
if (plugin.getConfig().getBoolean("rating-enabled", true))
|
||||
player.sendMessage(plugin.color("&e/ticket rate <ID> <good|bad> &7– Support bewerten"));
|
||||
|
||||
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.color("&e/ticket list &7– Ticket-Übersicht (GUI)"));
|
||||
player.sendMessage(plugin.color("&e/ticket claim <ID> &7– Ticket annehmen"));
|
||||
player.sendMessage(plugin.color("&e/ticket close <ID> &7– Ticket schließen"));
|
||||
player.sendMessage(plugin.color("&e/ticket close <ID> [Kommentar] &7– Ticket schließen"));
|
||||
}
|
||||
if (player.hasPermission("ticket.admin")) {
|
||||
player.sendMessage(plugin.color("&e/ticket forward <ID> <Spieler> &7– Ticket weiterleiten"));
|
||||
player.sendMessage(plugin.color("&e/ticket blacklist <add|remove|list> [Spieler] [Grund] &7– Blacklist verwalten"));
|
||||
player.sendMessage(plugin.color("&e/ticket reload &7– Konfiguration neu laden"));
|
||||
player.sendMessage(plugin.color("&e/ticket stats &7– Statistiken anzeigen"));
|
||||
}
|
||||
player.sendMessage(plugin.color("&8&m "));
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/main/java/de/ticketsystem/model/ConfigCategory.java
Normal file
53
src/main/java/de/ticketsystem/model/ConfigCategory.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package de.ticketsystem.model;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
/**
|
||||
* Eine aus der config.yml geladene Ticket-Kategorie.
|
||||
* Ersetzt das hardcodierte TicketCategory-Enum vollständig.
|
||||
*
|
||||
* Konfigurationsbeispiel (config.yml):
|
||||
*
|
||||
* categories:
|
||||
* bug:
|
||||
* name: "Bug"
|
||||
* color: "&c"
|
||||
* material: "REDSTONE"
|
||||
* aliases:
|
||||
* - "bug"
|
||||
* - "fehler"
|
||||
*/
|
||||
public class ConfigCategory {
|
||||
|
||||
/** Interner Schlüssel aus der Config (z.B. "bug", "general") – immer Kleinbuchstaben */
|
||||
private final String key;
|
||||
|
||||
/** Anzeigename (z.B. "Bug", "Allgemein") */
|
||||
private final String name;
|
||||
|
||||
/** Minecraft-Farbcode (z.B. "&c") */
|
||||
private final String color;
|
||||
|
||||
/** GUI-Item-Material */
|
||||
private final Material material;
|
||||
|
||||
public ConfigCategory(String key, String name, String color, Material material) {
|
||||
this.key = key.toLowerCase();
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
this.material = material;
|
||||
}
|
||||
|
||||
public String getKey() { return key; }
|
||||
public String getName() { return name; }
|
||||
public String getColor() { return color; }
|
||||
public Material getMaterial() { return material; }
|
||||
|
||||
/** Gibt den farbigen Anzeigenamen zurück, z.B. "§cBug" */
|
||||
public String getColored() {
|
||||
return org.bukkit.ChatColor.translateAlternateColorCodes('&', color + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return key; }
|
||||
}
|
||||
@@ -3,106 +3,176 @@ package de.ticketsystem.model;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.configuration.serialization.SerializableAs;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Ticket {
|
||||
|
||||
private int id;
|
||||
private UUID creatorUUID;
|
||||
@SerializableAs("Ticket")
|
||||
public class Ticket implements ConfigurationSerializable {
|
||||
|
||||
private int id;
|
||||
private UUID creatorUUID;
|
||||
private String creatorName;
|
||||
private String message;
|
||||
|
||||
// Location-Felder (werden separat gespeichert)
|
||||
private String worldName;
|
||||
private double x, y, z;
|
||||
private float yaw, pitch;
|
||||
private float yaw, pitch;
|
||||
|
||||
private TicketStatus status;
|
||||
private UUID claimerUUID;
|
||||
private String claimerName;
|
||||
private UUID forwardedToUUID;
|
||||
private String forwardedToName;
|
||||
private Timestamp createdAt;
|
||||
private Timestamp claimedAt;
|
||||
private Timestamp closedAt;
|
||||
private UUID claimerUUID;
|
||||
private String claimerName;
|
||||
private UUID forwardedToUUID;
|
||||
private String forwardedToName;
|
||||
private Timestamp createdAt;
|
||||
private Timestamp claimedAt;
|
||||
private Timestamp closedAt;
|
||||
private String closeComment;
|
||||
|
||||
private boolean playerDeleted = false;
|
||||
|
||||
/** Kategorie-Key aus config.yml, z.B. "bug", "general" */
|
||||
private String categoryKey = "general";
|
||||
|
||||
private TicketPriority priority = TicketPriority.NORMAL;
|
||||
|
||||
/** null = nicht bewertet | "THUMBS_UP" | "THUMBS_DOWN" */
|
||||
private String playerRating = null;
|
||||
private boolean claimerNotified = false;
|
||||
|
||||
public Ticket() {}
|
||||
|
||||
public Ticket(UUID creatorUUID, String creatorName, String message, Location location) {
|
||||
this.creatorUUID = creatorUUID;
|
||||
this.creatorName = creatorName;
|
||||
this.message = message;
|
||||
this.worldName = location.getWorld().getName();
|
||||
this.x = location.getX();
|
||||
this.y = location.getY();
|
||||
this.z = location.getZ();
|
||||
this.yaw = location.getYaw();
|
||||
this.pitch = location.getPitch();
|
||||
this.status = TicketStatus.OPEN;
|
||||
this.message = message;
|
||||
this.worldName = location.getWorld().getName();
|
||||
this.x = location.getX(); this.y = location.getY(); this.z = location.getZ();
|
||||
this.yaw = location.getYaw(); this.pitch = location.getPitch();
|
||||
this.status = TicketStatus.OPEN;
|
||||
this.createdAt = new Timestamp(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
World world = Bukkit.getWorld(worldName);
|
||||
if (world == null) return null;
|
||||
return new Location(world, x, y, z, yaw, pitch);
|
||||
public Ticket(Map<String, Object> map) {
|
||||
this.id = (int) map.get("id");
|
||||
Object cObj = map.get("creatorUUID");
|
||||
this.creatorUUID = cObj instanceof UUID ? (UUID) cObj : UUID.fromString((String) cObj);
|
||||
this.creatorName = (String) map.get("creatorName");
|
||||
this.message = (String) map.get("message");
|
||||
this.worldName = (String) map.get("world");
|
||||
this.x = toDouble(map.get("x")); this.y = toDouble(map.get("y")); this.z = toDouble(map.get("z"));
|
||||
this.yaw = toFloat(map.get("yaw")); this.pitch = toFloat(map.get("pitch"));
|
||||
this.status = TicketStatus.valueOf((String) map.get("status"));
|
||||
if (map.get("createdAt") != null) this.createdAt = new Timestamp(toLong(map.get("createdAt")));
|
||||
if (map.get("claimedAt") != null) this.claimedAt = new Timestamp(toLong(map.get("claimedAt")));
|
||||
if (map.get("closedAt") != null) this.closedAt = new Timestamp(toLong(map.get("closedAt")));
|
||||
this.closeComment = (String) map.get("closeComment");
|
||||
if (map.containsKey("claimerUUID") && map.get("claimerUUID") != null) {
|
||||
Object o = map.get("claimerUUID");
|
||||
this.claimerUUID = o instanceof UUID ? (UUID) o : UUID.fromString((String) o);
|
||||
this.claimerName = (String) map.get("claimerName");
|
||||
}
|
||||
if (map.containsKey("forwardedToUUID") && map.get("forwardedToUUID") != null) {
|
||||
Object o = map.get("forwardedToUUID");
|
||||
this.forwardedToUUID = o instanceof UUID ? (UUID) o : UUID.fromString((String) o);
|
||||
this.forwardedToName = (String) map.get("forwardedToName");
|
||||
}
|
||||
if (map.containsKey("playerDeleted")) this.playerDeleted = (boolean) map.get("playerDeleted");
|
||||
if (map.containsKey("category")) this.categoryKey = (String) map.get("category");
|
||||
if (map.containsKey("priority")) this.priority = TicketPriority.fromString((String) map.get("priority"));
|
||||
if (map.containsKey("playerRating")) this.playerRating = (String) map.get("playerRating");
|
||||
if (map.containsKey("claimerNotified")) this.claimerNotified = (boolean) map.get("claimerNotified");
|
||||
}
|
||||
|
||||
// ─────────────────────────── Getter & Setter ────────────────────────────
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("id", id);
|
||||
map.put("creatorUUID", creatorUUID.toString());
|
||||
map.put("creatorName", creatorName);
|
||||
map.put("message", message);
|
||||
map.put("world", worldName);
|
||||
map.put("x", x); map.put("y", y); map.put("z", z);
|
||||
map.put("yaw", yaw); map.put("pitch", pitch);
|
||||
map.put("status", status.name());
|
||||
if (createdAt != null) map.put("createdAt", createdAt.getTime());
|
||||
if (claimedAt != null) map.put("claimedAt", claimedAt.getTime());
|
||||
if (closedAt != null) map.put("closedAt", closedAt.getTime());
|
||||
if (closeComment != null) map.put("closeComment", closeComment);
|
||||
if (claimerUUID != null) { map.put("claimerUUID", claimerUUID.toString()); map.put("claimerName", claimerName); }
|
||||
if (forwardedToUUID != null) { map.put("forwardedToUUID", forwardedToUUID.toString()); map.put("forwardedToName", forwardedToName); }
|
||||
map.put("playerDeleted", playerDeleted);
|
||||
map.put("category", categoryKey);
|
||||
map.put("priority", priority.name());
|
||||
if (playerRating != null) map.put("playerRating", playerRating);
|
||||
map.put("claimerNotified", claimerNotified);
|
||||
return map;
|
||||
}
|
||||
|
||||
public int getId() { return id; }
|
||||
public void setId(int id) { this.id = id; }
|
||||
public static void register() { ConfigurationSerialization.registerClass(Ticket.class, "Ticket"); }
|
||||
|
||||
public UUID getCreatorUUID() { return creatorUUID; }
|
||||
public void setCreatorUUID(UUID creatorUUID) { this.creatorUUID = creatorUUID; }
|
||||
public Location getLocation() {
|
||||
World world = Bukkit.getWorld(worldName);
|
||||
return world == null ? null : new Location(world, x, y, z, yaw, pitch);
|
||||
}
|
||||
|
||||
public String getCreatorName() { return creatorName; }
|
||||
public void setCreatorName(String creatorName) { this.creatorName = creatorName; }
|
||||
private static double toDouble(Object o) { return o instanceof Double d ? d : ((Number) o).doubleValue(); }
|
||||
private static float toFloat(Object o) { return o instanceof Float f ? f : ((Number) o).floatValue(); }
|
||||
private static long toLong(Object o) { return ((Number) o).longValue(); }
|
||||
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
|
||||
public String getWorldName() { return worldName; }
|
||||
public void setWorldName(String worldName) { this.worldName = worldName; }
|
||||
|
||||
public double getX() { return x; }
|
||||
public void setX(double x) { this.x = x; }
|
||||
|
||||
public double getY() { return y; }
|
||||
public void setY(double y) { this.y = y; }
|
||||
|
||||
public double getZ() { return z; }
|
||||
public void setZ(double z) { this.z = z; }
|
||||
|
||||
public float getYaw() { return yaw; }
|
||||
public void setYaw(float yaw) { this.yaw = yaw; }
|
||||
|
||||
public float getPitch() { return pitch; }
|
||||
public void setPitch(float pitch) { this.pitch = pitch; }
|
||||
|
||||
public TicketStatus getStatus() { return status; }
|
||||
public void setStatus(TicketStatus status) { this.status = status; }
|
||||
|
||||
public UUID getClaimerUUID() { return claimerUUID; }
|
||||
public void setClaimerUUID(UUID claimerUUID) { this.claimerUUID = claimerUUID; }
|
||||
|
||||
public String getClaimerName() { return claimerName; }
|
||||
public void setClaimerName(String claimerName) { this.claimerName = claimerName; }
|
||||
|
||||
public UUID getForwardedToUUID() { return forwardedToUUID; }
|
||||
public void setForwardedToUUID(UUID forwardedToUUID) { this.forwardedToUUID = forwardedToUUID; }
|
||||
|
||||
public String getForwardedToName() { return forwardedToName; }
|
||||
public void setForwardedToName(String forwardedToName) { this.forwardedToName = forwardedToName; }
|
||||
|
||||
public Timestamp getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public Timestamp getClaimedAt() { return claimedAt; }
|
||||
public void setClaimedAt(Timestamp claimedAt) { this.claimedAt = claimedAt; }
|
||||
|
||||
public Timestamp getClosedAt() { return closedAt; }
|
||||
public void setClosedAt(Timestamp closedAt) { this.closedAt = closedAt; }
|
||||
}
|
||||
public int getId() { return id; }
|
||||
public void setId(int id) { this.id = id; }
|
||||
public UUID getCreatorUUID() { return creatorUUID; }
|
||||
public void setCreatorUUID(UUID v) { this.creatorUUID = v; }
|
||||
public String getCreatorName() { return creatorName; }
|
||||
public void setCreatorName(String v) { this.creatorName = v; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String v) { this.message = v; }
|
||||
public String getWorldName() { return worldName; }
|
||||
public void setWorldName(String v) { this.worldName = v; }
|
||||
public double getX() { return x; }
|
||||
public void setX(double v) { this.x = v; }
|
||||
public double getY() { return y; }
|
||||
public void setY(double v) { this.y = v; }
|
||||
public double getZ() { return z; }
|
||||
public void setZ(double v) { this.z = v; }
|
||||
public float getYaw() { return yaw; }
|
||||
public void setYaw(float v) { this.yaw = v; }
|
||||
public float getPitch() { return pitch; }
|
||||
public void setPitch(float v) { this.pitch = v; }
|
||||
public TicketStatus getStatus() { return status; }
|
||||
public void setStatus(TicketStatus v) { this.status = v; }
|
||||
public UUID getClaimerUUID() { return claimerUUID; }
|
||||
public void setClaimerUUID(UUID v) { this.claimerUUID = v; }
|
||||
public String getClaimerName() { return claimerName; }
|
||||
public void setClaimerName(String v) { this.claimerName = v; }
|
||||
public UUID getForwardedToUUID() { return forwardedToUUID; }
|
||||
public void setForwardedToUUID(UUID v) { this.forwardedToUUID = v; }
|
||||
public String getForwardedToName() { return forwardedToName; }
|
||||
public void setForwardedToName(String v) { this.forwardedToName = v; }
|
||||
public Timestamp getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(Timestamp v) { this.createdAt = v; }
|
||||
public Timestamp getClaimedAt() { return claimedAt; }
|
||||
public void setClaimedAt(Timestamp v) { this.claimedAt = v; }
|
||||
public Timestamp getClosedAt() { return closedAt; }
|
||||
public void setClosedAt(Timestamp v) { this.closedAt = v; }
|
||||
public String getCloseComment() { return closeComment; }
|
||||
public void setCloseComment(String v) { this.closeComment = v; }
|
||||
public boolean isPlayerDeleted() { return playerDeleted; }
|
||||
public void setPlayerDeleted(boolean v) { this.playerDeleted = v; }
|
||||
public String getCategoryKey() { return categoryKey; }
|
||||
public void setCategoryKey(String v) { this.categoryKey = v != null ? v.toLowerCase() : "general"; }
|
||||
public TicketPriority getPriority() { return priority; }
|
||||
public void setPriority(TicketPriority v) { this.priority = v; }
|
||||
public String getPlayerRating() { return playerRating; }
|
||||
public void setPlayerRating(String v) { this.playerRating = v; }
|
||||
public boolean hasRating() { return playerRating != null; }
|
||||
public boolean isClaimerNotified() { return claimerNotified; }
|
||||
public void setClaimerNotified(boolean v) { this.claimerNotified = v; }
|
||||
}
|
||||
33
src/main/java/de/ticketsystem/model/TicketCategory.java
Normal file
33
src/main/java/de/ticketsystem/model/TicketCategory.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package de.ticketsystem.model;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
public enum TicketCategory {
|
||||
GENERAL ("Allgemein", "§7", Material.PAPER),
|
||||
BUG ("Bug", "§c", Material.REDSTONE),
|
||||
QUESTION ("Frage", "§e", Material.BOOK),
|
||||
COMPLAINT ("Beschwerde", "§6", Material.WRITABLE_BOOK),
|
||||
OTHER ("Sonstiges", "§8", Material.FEATHER);
|
||||
|
||||
private final String displayName;
|
||||
private final String color;
|
||||
private final Material guiMaterial;
|
||||
|
||||
TicketCategory(String displayName, String color, Material guiMaterial) {
|
||||
this.displayName = displayName;
|
||||
this.color = color;
|
||||
this.guiMaterial = guiMaterial;
|
||||
}
|
||||
|
||||
public String getDisplayName() { return displayName; }
|
||||
public String getColor() { return color; }
|
||||
public String getColored() { return color + displayName; }
|
||||
public Material getGuiMaterial() { return guiMaterial; }
|
||||
|
||||
/** Safely parse from stored string, fall back to GENERAL. */
|
||||
public static TicketCategory fromString(String s) {
|
||||
if (s == null) return GENERAL;
|
||||
try { return valueOf(s.toUpperCase()); }
|
||||
catch (IllegalArgumentException e) { return GENERAL; }
|
||||
}
|
||||
}
|
||||
47
src/main/java/de/ticketsystem/model/TicketComment.java
Normal file
47
src/main/java/de/ticketsystem/model/TicketComment.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package de.ticketsystem.model;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a player comment/reply on a ticket.
|
||||
*/
|
||||
public class TicketComment {
|
||||
|
||||
private int id;
|
||||
private int ticketId;
|
||||
private UUID authorUUID;
|
||||
private String authorName;
|
||||
private String message;
|
||||
private Timestamp createdAt;
|
||||
|
||||
public TicketComment() {}
|
||||
|
||||
public TicketComment(int ticketId, UUID authorUUID, String authorName, String message) {
|
||||
this.ticketId = ticketId;
|
||||
this.authorUUID = authorUUID;
|
||||
this.authorName = authorName;
|
||||
this.message = message;
|
||||
this.createdAt = new Timestamp(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
// ─────────────── Getters / Setters ────────────────────────────────────
|
||||
|
||||
public int getId() { return id; }
|
||||
public void setId(int id) { this.id = id; }
|
||||
|
||||
public int getTicketId() { return ticketId; }
|
||||
public void setTicketId(int ticketId) { this.ticketId = ticketId; }
|
||||
|
||||
public UUID getAuthorUUID() { return authorUUID; }
|
||||
public void setAuthorUUID(UUID authorUUID) { this.authorUUID = authorUUID; }
|
||||
|
||||
public String getAuthorName() { return authorName; }
|
||||
public void setAuthorName(String authorName){ this.authorName = authorName; }
|
||||
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
|
||||
public Timestamp getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(Timestamp ts) { this.createdAt = ts; }
|
||||
}
|
||||
31
src/main/java/de/ticketsystem/model/TicketPriority.java
Normal file
31
src/main/java/de/ticketsystem/model/TicketPriority.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package de.ticketsystem.model;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
public enum TicketPriority {
|
||||
LOW ("Niedrig", "§a", Material.GREEN_WOOL),
|
||||
NORMAL ("Normal", "§e", Material.YELLOW_WOOL),
|
||||
HIGH ("Hoch", "§6", Material.ORANGE_WOOL),
|
||||
URGENT ("Dringend","§c", Material.RED_WOOL);
|
||||
|
||||
private final String displayName;
|
||||
private final String color;
|
||||
private final Material guiMaterial;
|
||||
|
||||
TicketPriority(String displayName, String color, Material guiMaterial) {
|
||||
this.displayName = displayName;
|
||||
this.color = color;
|
||||
this.guiMaterial = guiMaterial;
|
||||
}
|
||||
|
||||
public String getDisplayName() { return displayName; }
|
||||
public String getColor() { return color; }
|
||||
public String getColored() { return color + displayName; }
|
||||
public Material getGuiMaterial() { return guiMaterial; }
|
||||
|
||||
public static TicketPriority fromString(String s) {
|
||||
if (s == null) return NORMAL;
|
||||
try { return valueOf(s.toUpperCase()); }
|
||||
catch (IllegalArgumentException e) { return NORMAL; }
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,125 @@ max-open-tickets-per-player: 2 # Maximale offene Tickets pro Spieler (0 = unbeg
|
||||
# ----------------------------------------------------
|
||||
auto-archive-interval-hours: 24 # Intervall in Stunden (0 = aus)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# OPTIONALE FEATURES
|
||||
# ----------------------------------------------------
|
||||
|
||||
# Kategorie-System (true = aktiviert)
|
||||
# Spieler können beim Erstellen eine Kategorie wählen: /ticket create [kategorie] [priorität] <text>
|
||||
categories-enabled: true
|
||||
|
||||
# Prioritäten-System (true = aktiviert)
|
||||
# Spieler können beim Erstellen eine Priorität wählen: /ticket create [kategorie] [priorität] <text>
|
||||
# Admins/Supporter können die Priorität nachträglich ändern: /ticket setpriority <id> <low|normal|high|urgent>
|
||||
priorities-enabled: true
|
||||
|
||||
# Bewertungs-System (true = aktiviert)
|
||||
# Spieler können nach dem Schließen den Support bewerten: /ticket rate <id> good|bad
|
||||
# Ergebnisse sind in /ticket stats sichtbar
|
||||
rating-enabled: true
|
||||
|
||||
# ----------------------------------------------------
|
||||
# KATEGORIEN (nur aktiv wenn categories-enabled: true)
|
||||
# ----------------------------------------------------
|
||||
# Jede Kategorie hat:
|
||||
# name: Anzeigename im Chat und in der GUI
|
||||
# color: Farbcode mit & (Minecraft Farbcodes)
|
||||
# material: Minecraft-Material für das GUI-Item (Großbuchstaben, z.B. PAPER, REDSTONE, BOOK)
|
||||
# aliases: Alternative Eingaben beim /ticket create Befehl (Kleinbuchstaben!)
|
||||
#
|
||||
# Das erste eingetragene Item ist die Standard-Kategorie für Tickets ohne Angabe.
|
||||
# Du kannst beliebig viele Kategorien hinzufügen oder entfernen.
|
||||
# ----------------------------------------------------
|
||||
categories:
|
||||
general:
|
||||
name: "Allgemein"
|
||||
color: "&7"
|
||||
material: "PAPER"
|
||||
aliases:
|
||||
- "allgemein"
|
||||
- "general"
|
||||
- "default"
|
||||
bug:
|
||||
name: "Bug"
|
||||
color: "&c"
|
||||
material: "REDSTONE"
|
||||
aliases:
|
||||
- "bug"
|
||||
- "fehler"
|
||||
- "error"
|
||||
question:
|
||||
name: "Frage"
|
||||
color: "&e"
|
||||
material: "BOOK"
|
||||
aliases:
|
||||
- "frage"
|
||||
- "question"
|
||||
- "help"
|
||||
- "hilfe"
|
||||
complaint:
|
||||
name: "Beschwerde"
|
||||
color: "&6"
|
||||
material: "WRITABLE_BOOK"
|
||||
aliases:
|
||||
- "beschwerde"
|
||||
- "complaint"
|
||||
- "report"
|
||||
- "melden"
|
||||
other:
|
||||
name: "Sonstiges"
|
||||
color: "&8"
|
||||
material: "FEATHER"
|
||||
aliases:
|
||||
- "sonstiges"
|
||||
- "other"
|
||||
- "misc"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# DISCORD WEBHOOK (Optional)
|
||||
# ----------------------------------------------------
|
||||
discord:
|
||||
# Auf true setzen um Discord-Benachrichtigungen zu aktivieren
|
||||
enabled: false
|
||||
|
||||
# Webhook-URL aus Discord (Kanaleinstellungen → Integrationen → Webhook erstellen)
|
||||
webhook-url: ""
|
||||
|
||||
# Rollen-Ping: Discord-Rollen-ID (Rechtsklick auf Rolle → ID kopieren)
|
||||
# Leer lassen ("") = kein Ping
|
||||
role-ping-id: ""
|
||||
|
||||
messages:
|
||||
# ── Neues Ticket ────────────────────────────────────────────────────────
|
||||
new-ticket:
|
||||
title: "🎫 Neues Ticket erstellt"
|
||||
color: "3066993" # Grün
|
||||
footer: "TicketSystem"
|
||||
show-position: true # Welt & Koordinaten im Embed anzeigen
|
||||
show-category: true # Kategorie im Embed anzeigen
|
||||
show-priority: true # Priorität im Embed anzeigen
|
||||
role-ping: false # Rollen-Ping bei neuem Ticket senden
|
||||
|
||||
# ── Ticket geschlossen ──────────────────────────────────────────────────
|
||||
ticket-closed:
|
||||
enabled: false # Webhook-Nachricht beim Schließen senden
|
||||
title: "🔒 Ticket geschlossen"
|
||||
color: "15158332" # Rot
|
||||
footer: "TicketSystem"
|
||||
show-category: true # Kategorie im Embed anzeigen
|
||||
show-priority: true # Priorität im Embed anzeigen
|
||||
role-ping: false # Rollen-Ping beim Schließen senden
|
||||
|
||||
# ── Ticket weitergeleitet ───────────────────────────────────────────────
|
||||
ticket-forwarded:
|
||||
enabled: false # Webhook-Nachricht beim Weiterleiten senden
|
||||
title: "🔀 Ticket weitergeleitet"
|
||||
color: "15105570" # Orange
|
||||
footer: "TicketSystem"
|
||||
show-category: true # Kategorie im Embed anzeigen
|
||||
show-priority: true # Priorität im Embed anzeigen
|
||||
role-ping: false # Rollen-Ping beim Weiterleiten senden
|
||||
|
||||
# ----------------------------------------------------
|
||||
# SYSTEM-NACHRICHTEN (mit &-Farbcodes)
|
||||
# ----------------------------------------------------
|
||||
@@ -82,13 +201,42 @@ messages:
|
||||
ticket-claimed-notify: "&aDein Ticket &e#{id} &awurde von &e{claimer} &aangenommen."
|
||||
ticket-closed: "&aTicket &e#{id} &awurde geschlossen."
|
||||
ticket-forwarded: "&aTicket &e#{id} &awurde an &e{player} &aweitergeleitet."
|
||||
ticket-forwarded-notify: "&eDu hast ein Ticket von &6{player} &eweitergeleitet bekommen."
|
||||
ticket-forwarded-notify: "&eDu hast ein Ticket von &6{player} &eweitergeleitet bekommen. &7(ID: {id})"
|
||||
|
||||
# --- BENACHRICHTIGUNGEN FÜR DEN TICKET-ERSTELLER ---
|
||||
ticket-closed-notify: "&aDein Ticket &e#{id} &awurde geschlossen."
|
||||
ticket-forwarded-creator-notify: "&eDein Ticket &6#{id} &ewurde an &b{supporter} &eweitergeleitet."
|
||||
|
||||
# --- KATEGORIEN ---
|
||||
# {category} wird durch den Anzeigenamen der gewählten Kategorie ersetzt
|
||||
ticket-created-category: "&aTicket &e#{id} &aerstellt! &7Kategorie: {category}"
|
||||
category-invalid: "&cUnbekannte Kategorie: &e{input}&c. Verfügbare Kategorien: &e{categories}"
|
||||
|
||||
# --- KOMMENTARE ---
|
||||
comment-saved: "&aDein Kommentar zu Ticket &e#{id} &awurde gespeichert."
|
||||
comment-notify: "&e[Ticket #{id}] &f{author} &7kommentiert: &f{message}"
|
||||
comment-no-permission: "&cDu kannst nur deine eigenen Tickets kommentieren."
|
||||
|
||||
# --- BEWERTUNGEN ---
|
||||
rating-saved-good: "&aDanke für deine Bewertung! &a👍 Positiv"
|
||||
rating-saved-bad: "&aDanke für deine Bewertung! &c👎 Negativ"
|
||||
rating-already-rated: "&cDu hast dieses Ticket bereits bewertet."
|
||||
rating-not-yours: "&cDu kannst nur deine eigenen Tickets bewerten."
|
||||
rating-disabled: "&cBewertungen sind aktuell deaktiviert."
|
||||
rating-prompt: "&6Wie zufrieden bist du mit dem Support?\n&a/ticket rate {id} good &7– 👍 Gut\n&c/ticket rate {id} bad &7– 👎 Schlecht"
|
||||
|
||||
# --- BLACKLIST ---
|
||||
blacklist-added: "&a{player} &awurde zur Ticket-Blacklist hinzugefügt. &7Grund: &e{reason}"
|
||||
blacklist-removed: "&a{player} &awurde von der Blacklist entfernt."
|
||||
blacklist-already: "&cSpieler ist bereits auf der Blacklist."
|
||||
blacklist-not-found: "&cSpieler war nicht auf der Blacklist."
|
||||
blacklist-blocked: "&cDu wurdest vom Ticket-System gesperrt und kannst keine Tickets erstellen."
|
||||
|
||||
# --- FEHLER & HINWEISE ---
|
||||
no-permission: "&cDu hast keine Berechtigung!"
|
||||
no-open-tickets: "&aAktuell gibt es keine offenen Tickets."
|
||||
join-open-tickets: "&eEs gibt noch &6{count} &eoffene Ticket(s)!"
|
||||
new-ticket-notify: "&e{player} &ahat ein neues Ticket erstellt: &7{message}"
|
||||
new-ticket-notify: "&e{player} &ahat ein neues Ticket erstellt: &7{message} &7(ID: &e{id}&7)"
|
||||
already-claimed: "&cDieses Ticket wurde bereits geclaimt!"
|
||||
ticket-not-found: "&cTicket nicht gefunden!"
|
||||
cooldown: "&cBitte warte &e{seconds} Sekunden &cbevor du ein neues Ticket erstellst."
|
||||
cooldown: "&cBitte warte &e{seconds} Sekunden &cbevor du ein neues Ticket erstellst."
|
||||
@@ -1,5 +1,5 @@
|
||||
name: TicketSystem
|
||||
version: 1.0.1
|
||||
version: 1.0.4
|
||||
main: de.ticketsystem.TicketPlugin
|
||||
api-version: 1.20
|
||||
author: M_Viper
|
||||
@@ -7,19 +7,50 @@ description: Ingame Support Ticket System with MySQL
|
||||
|
||||
commands:
|
||||
ticket:
|
||||
description: Ticket System Hauptbefehl
|
||||
usage: /ticket <create|list|claim|close|forward|reload>
|
||||
description: TicketSystem Hauptbefehl
|
||||
usage: |
|
||||
/ticket create [Kategorie] <Text>
|
||||
/ticket list
|
||||
/ticket comment <ID> <Nachricht>
|
||||
/ticket rate <ID> <good|bad>
|
||||
/ticket claim <ID>
|
||||
/ticket close <ID> [Kommentar]
|
||||
/ticket forward <ID> <Spieler>
|
||||
/ticket blacklist <add|remove|list> [Spieler] [Grund]
|
||||
/ticket stats
|
||||
/ticket archive
|
||||
/ticket reload
|
||||
aliases: [t, support]
|
||||
|
||||
permissions:
|
||||
|
||||
# ── Spieler-Permissions ───────────────────────────────────────────────────
|
||||
|
||||
ticket.create:
|
||||
description: Spieler kann Tickets erstellen
|
||||
description: Spieler kann Tickets erstellen und kommentieren
|
||||
default: true
|
||||
|
||||
# ── Supporter-Permissions ─────────────────────────────────────────────────
|
||||
|
||||
ticket.support:
|
||||
description: Supporter kann Tickets einsehen und claimen
|
||||
description: Supporter kann Tickets einsehen, claimen und schließen
|
||||
default: false
|
||||
|
||||
ticket.archive:
|
||||
description: Zugriff auf das Ticket-Archiv (öffnen, einsehen, permanent löschen)
|
||||
default: false
|
||||
|
||||
# ── Admin-Permissions ────────────────────────────────────────────────────
|
||||
|
||||
ticket.admin:
|
||||
description: Admin hat vollen Zugriff inkl. Weiterleitung und Reload
|
||||
description: >
|
||||
Admin hat vollen Zugriff: Weiterleiten, Blacklist verwalten,
|
||||
Statistiken, Reload, Archiv, Export/Import, Migration
|
||||
default: op
|
||||
children:
|
||||
ticket.support: true
|
||||
ticket.blacklist: true
|
||||
|
||||
ticket.blacklist:
|
||||
description: Kann Spieler zur Ticket-Blacklist hinzufügen und entfernen
|
||||
default: false
|
||||
Reference in New Issue
Block a user