Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a91e17a097 | |||
| 12c9379797 | |||
| 535b0aa2f3 | |||
| d14646c5ae | |||
| df6878db2f | |||
| 526cb8b442 | |||
| b930793c50 | |||
| 566941d687 | |||
| dc732c1410 | |||
| d7fb6940cd |
223
README.md
223
README.md
@@ -1,99 +1,182 @@
|
|||||||
# TicketSystem
|
# TicketSystem
|
||||||
|
|
||||||
[]() []() []()
|
  
|
||||||
|
|
||||||
**TicketSystem** ist das umfassende 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.
|
**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.
|
||||||
|
|
||||||
## Features (Details)
|
## Features
|
||||||
|
|
||||||
- **MySQL oder Datei-Speicherung** (YAML/JSON) – jederzeit umschaltbar, Migration und Backup inklusive
|
- **MySQL oder Datei-Speicherung** – YAML/JSON oder MySQL/MariaDB, jederzeit umschaltbar, Migration & Backup inklusive
|
||||||
- **Automatische Backups & Migration** zwischen Speicherarten, Datenverlust ausgeschlossen
|
- **Automatische Backups & Migration** – Sicheres Wechseln zwischen Speicherarten, Datenverlust ausgeschlossen
|
||||||
- **Export/Import** von Tickets (z.B. für Server-Umzüge oder Testumgebungen)
|
- **Export/Import** – Tickets einfach zwischen Servern oder Instanzen übertragen
|
||||||
- **Statistiken & Archivierung** (inkl. automatischer Archivierung nach Zeitplan, manuelles Archivieren möglich)
|
- **Statistiken & Archivierung** – Übersichtliche Auswertung, automatische Archivierung nach Zeitplan, manuelles Archivieren möglich
|
||||||
- **Konfigurierbare Speicherpfade** für Daten und Archive (relativ oder absolut)
|
- **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
|
||||||
- **Vollständige Validierung** der Daten beim Laden (Fehlerausgabe im Log & Chat, fehlerhafte Tickets werden übersprungen)
|
- **Konfigurierbare Speicherpfade** – Daten- und Archivdateien frei wählbar, auch absolute Pfade
|
||||||
- **Bessere Fehlerausgaben** für Admins im Chat und Log (inkl. Validierungs- und Speicherfehler)
|
- **Vollständige Validierung** – Fehlerhafte Tickets werden beim Laden erkannt, gemeldet und übersprungen
|
||||||
- **Debug-Modus & Versionsprüfung** für Entwickler und Admins (veraltete config.yml wird erkannt)
|
- **Bessere Fehlerausgaben** – Alle Fehler erscheinen im Log und für Admins im Chat, inkl. Validierungs- und Speicherfehler
|
||||||
- **Komplett anpassbar** (Nachrichten, Farben, Limits, Speicherpfade, Archiv-Intervall, Cooldowns, Rechte)
|
- **Debug-Modus & Versionsprüfung** – Für Entwickler und Admins, erkennt veraltete config.yml automatisch
|
||||||
- **Unit-Tests** für die Speicher-Logik (maximale Zuverlässigkeit)
|
- **Komplett anpassbar** – Nachrichten, Farben, Limits, Speicherpfade, Archiv-Intervall, Cooldowns, Rechte
|
||||||
- **Dynamische GUI**: Die Ticket-GUI passt sich automatisch der Ticketanzahl an (bis zu 54 Tickets pro Seite, Blättern bei Bedarf)
|
- **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
|
||||||
- **Performance**: Optimiert für große Server, alle Operationen laufen asynchron und ressourcenschonend
|
- **Seiten-System** – Bei sehr vielen Tickets wird automatisch geblättert
|
||||||
- **Support & Erweiterbarkeit**: Sauberer Code, viele Hooks für eigene Erweiterungen
|
- **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
|
||||||
|
|
||||||
## Einrichtung & Konfiguration
|
---
|
||||||
|
|
||||||
1. **config.yml** anpassen:
|
## Installation & Setup
|
||||||
- `data-file`, `archive-file`: Speicherorte für Tickets und Archive (relativ oder absolut)
|
|
||||||
- `use-mysql`: true/false
|
|
||||||
- `auto-archive-interval-hours`: Intervall für automatische Archivierung (0 = aus)
|
|
||||||
- `debug`: true/false für ausführliche Logs
|
|
||||||
- `version`: Versionsprüfung für die config.yml
|
|
||||||
- Nachrichten, Farben, Limits, Cooldowns, Rechte individuell anpassbar
|
|
||||||
2. **MySQL** (optional): Zugangsdaten eintragen
|
|
||||||
3. **/ticket**-Befehl nutzen (inkl. Admin-Tools für Migration, Export, Import, Statistik, Archiv, Reload)
|
|
||||||
|
|
||||||
## Kompatibilität & Zielgruppe
|
1. **TicketSystem.jar** in den `plugins`-Ordner legen und Server starten
|
||||||
|
2. **config.yml** anpassen (Speicherorte, Nachrichten, Limits, Farben, MySQL-Daten etc.)
|
||||||
- Minecraft 1.18.x – 1.21.x (Paper, Spigot, Purpur)
|
3. **/ticket**-Befehle nutzen (siehe unten)
|
||||||
- Java 17+
|
|
||||||
- YAML/JSON oder MySQL/MariaDB
|
|
||||||
- Für Community-Server, Citybuild, Minigames, Survival, u.v.m.
|
|
||||||
|
|
||||||
## Support & Kontakt
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Befehle & Rechte
|
## Befehle & Rechte
|
||||||
|
|
||||||
- `/ticket` – Hauptbefehl für alle Ticket-Funktionen
|
### Spieler-Befehle
|
||||||
- `/ticket` – Übersicht aller offenen Tickets (GUI)
|
|
||||||
- `/ticket create <Nachricht>` – Erstellt ein neues Ticket
|
|
||||||
- `/ticket claim <ID>` – Ticket übernehmen
|
|
||||||
- `/ticket close <ID>` – Ticket schließen
|
|
||||||
- `/ticket forward <ID> <Spieler>` – Ticket weiterleiten
|
|
||||||
- `/ticket archive` – Tickets archivieren (manuell)
|
|
||||||
- `/ticket export <Datei>` – Tickets exportieren
|
|
||||||
- `/ticket import <Datei>` – Tickets importieren
|
|
||||||
- `/ticket migrate <tomysql|tofile>` – Migration zwischen Speicherarten
|
|
||||||
- `/ticket stats` – Statistiken anzeigen
|
|
||||||
- `/ticket reload` – Konfiguration neu laden
|
|
||||||
|
|
||||||
- Rechte:
|
```
|
||||||
- `ticket.admin` – Zugriff auf alle Admin- und Management-Funktionen
|
/ticket - Hilfe & Befehlsübersicht
|
||||||
- `ticket.use` – (Standard) Ticket erstellen und eigene Tickets verwalten
|
/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
|
||||||
|
```
|
||||||
|
|
||||||
## Beispiel-Konfiguration (Ausschnitt)
|
### 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
|
```yaml
|
||||||
version: "2.0"
|
discord:
|
||||||
debug: false
|
enabled: true
|
||||||
data-file: "data.yml"
|
webhook-url: "https://discord.com/api/webhooks/..."
|
||||||
archive-file: "archive.yml"
|
role-ping-id: "123456789012345678" # Discord-Rollen-ID (leer = kein Ping)
|
||||||
use-mysql: false
|
messages:
|
||||||
use-json: false
|
new-ticket:
|
||||||
auto-archive-interval-hours: 24
|
role-ping: true
|
||||||
prefix: "&8[&6Ticket&8] &r"
|
show-category: true
|
||||||
ticket-cooldown: 60
|
show-priority: true
|
||||||
max-description-length: 100
|
ticket-closed:
|
||||||
max-open-tickets-per-player: 2
|
enabled: true
|
||||||
|
role-ping: false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
**Kann ich zwischen MySQL und Datei-Speicherung wechseln?**
|
**Kann ich zwischen MySQL und Datei-Speicherung wechseln?**
|
||||||
> Ja, jederzeit per Migrationstool oder Befehl `/ticket migrate ...`.
|
> Ja! Mit `/ticket migrate tomysql` oder `/ticket migrate tofile` werden alle Daten automatisch migriert.
|
||||||
|
|
||||||
**Wie viele Tickets passen in die GUI?**
|
**Wie konfiguriere ich eigene Kategorien?**
|
||||||
> Bis zu 54 pro Seite, bei mehr Tickets wird geblättert.
|
> In der `config.yml` unter `categories:` — Name, Farbe, Material (für die GUI) und Aliases frei wählbar. Änderungen werden mit `/ticket reload` übernommen.
|
||||||
|
|
||||||
**Wie kann ich Nachrichten und Limits anpassen?**
|
**Was passiert mit Benachrichtigungen wenn ein Spieler offline ist?**
|
||||||
> Alle Texte, Farben und Limits findest du in der `config.yml`.
|
> Alle Kommentar-, Schließ- und Status-Benachrichtigungen werden gespeichert und beim nächsten Login gebündelt angezeigt.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
**Wie aktiviere ich den Debug-Modus?**
|
**Wie aktiviere ich den Debug-Modus?**
|
||||||
> Setze `debug: true` in der `config.yml`.
|
> Setze `debug: true` in der `config.yml`.
|
||||||
|
|
||||||
**Wie kann ich Tickets exportieren/importieren?**
|
**Wer darf das Ticket-Archiv sehen?**
|
||||||
> Mit `/ticket export <Datei>` und `/ticket import <Datei>`.
|
> Nur Spieler mit der Permission `ticket.archive`. Diese wird weder automatisch an OPs noch an Admins vergeben und muss explizit zugewiesen werden.
|
||||||
|
|
||||||
## Support & Kontakt
|
---
|
||||||
|
|
||||||
Du hast Fragen, Feedback oder möchtest das Plugin erweitern? Melde dich direkt bei **Viper Plugins** – wir helfen schnell und unkompliziert!
|
## Vergleich mit anderen Plugins
|
||||||
|
|
||||||
|
| 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 & Community
|
||||||
|
|
||||||
|
- [Discord Support](https://discord.com/invite/FdRs4BRd8D)
|
||||||
|
- [Git Issues](https://git.viper.ipv64.net/M_Viper/TicketSystem/issues)
|
||||||
|
|
||||||
|
Wir antworten in der Regel innerhalb von 24 Stunden!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⭐ Unterstütze das Projekt
|
||||||
|
|
||||||
|
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>
|
<groupId>de.ticketsystem</groupId>
|
||||||
<artifactId>TicketSystem</artifactId>
|
<artifactId>TicketSystem</artifactId>
|
||||||
<version>1.0.1</version>
|
<version>1.0.3</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>TicketSystem</name>
|
<name>TicketSystem</name>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
|
||||||
package de.ticketsystem;
|
package de.ticketsystem;
|
||||||
|
|
||||||
import de.ticketsystem.commands.TicketCommand;
|
import de.ticketsystem.commands.TicketCommand;
|
||||||
import de.ticketsystem.database.DatabaseManager;
|
import de.ticketsystem.database.DatabaseManager;
|
||||||
|
import de.ticketsystem.discord.DiscordWebhook;
|
||||||
import de.ticketsystem.gui.TicketGUI;
|
import de.ticketsystem.gui.TicketGUI;
|
||||||
import de.ticketsystem.listeners.PlayerJoinListener;
|
import de.ticketsystem.listeners.PlayerJoinListener;
|
||||||
import de.ticketsystem.manager.TicketManager;
|
import de.ticketsystem.manager.TicketManager;
|
||||||
|
// WICHTIG: Import hinzugefügt
|
||||||
|
import de.ticketsystem.model.Ticket;
|
||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
@@ -19,15 +21,19 @@ public class TicketPlugin extends JavaPlugin {
|
|||||||
private DatabaseManager databaseManager;
|
private DatabaseManager databaseManager;
|
||||||
private TicketManager ticketManager;
|
private TicketManager ticketManager;
|
||||||
private TicketGUI ticketGUI;
|
private TicketGUI ticketGUI;
|
||||||
|
private DiscordWebhook discordWebhook;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
// Config speichern falls nicht vorhanden
|
|
||||||
saveDefaultConfig();
|
saveDefaultConfig();
|
||||||
|
|
||||||
// Update-Checker (Spigot Resource-ID anpassen!)
|
// --- WICHTIG: Ticket-Klasse registrieren ---
|
||||||
|
Ticket.register();
|
||||||
|
// -------------------------------------------
|
||||||
|
|
||||||
|
// Update-Checker
|
||||||
int resourceId = 132757;
|
int resourceId = 132757;
|
||||||
new UpdateChecker(this, resourceId).getVersion(version -> {
|
new UpdateChecker(this, resourceId).getVersion(version -> {
|
||||||
String current = getDescription().getVersion();
|
String current = getDescription().getVersion();
|
||||||
@@ -35,12 +41,11 @@ public class TicketPlugin extends JavaPlugin {
|
|||||||
String msg = ChatColor.translateAlternateColorCodes('&',
|
String msg = ChatColor.translateAlternateColorCodes('&',
|
||||||
"&6[TicketSystem] &eEs ist eine neue Version verfügbar: &a" + version + " &7(aktuell: " + current + ")");
|
"&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 + ")");
|
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().getScheduler().runTaskLater(this, () -> {
|
||||||
getServer().getOnlinePlayers().stream()
|
getServer().getOnlinePlayers().stream()
|
||||||
.filter(p -> p.hasPermission("ticket.admin"))
|
.filter(p -> p.hasPermission("ticket.admin"))
|
||||||
.forEach(p -> p.sendMessage(msg));
|
.forEach(p -> p.sendMessage(msg));
|
||||||
}, 20L); // 20 Ticks = 1 Sekunde
|
}, 20L);
|
||||||
} else {
|
} else {
|
||||||
getLogger().info("TicketSystem ist aktuell (Version " + current + ")");
|
getLogger().info("TicketSystem ist aktuell (Version " + current + ")");
|
||||||
}
|
}
|
||||||
@@ -50,46 +55,51 @@ public class TicketPlugin extends JavaPlugin {
|
|||||||
String configVersion = getConfig().getString("version", "");
|
String configVersion = getConfig().getString("version", "");
|
||||||
String expectedVersion = "2.0";
|
String expectedVersion = "2.0";
|
||||||
if (!expectedVersion.equals(configVersion)) {
|
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);
|
debug = getConfig().getBoolean("debug", false);
|
||||||
|
|
||||||
// Datenbankverbindung aufbauen
|
// Datenbankverbindung
|
||||||
databaseManager = new DatabaseManager(this);
|
databaseManager = new DatabaseManager(this);
|
||||||
if (!databaseManager.connect()) {
|
if (!databaseManager.connect()) {
|
||||||
getLogger().severe("Konnte keine Datenbankverbindung herstellen! Plugin läuft im Datei-Modus weiter.");
|
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
|
// Manager, GUI & Discord-Webhook initialisieren
|
||||||
ticketManager = new TicketManager(this);
|
ticketManager = new TicketManager(this);
|
||||||
ticketGUI = new TicketGUI(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);
|
TicketCommand ticketCommand = new TicketCommand(this);
|
||||||
Objects.requireNonNull(getCommand("ticket")).setExecutor(ticketCommand);
|
Objects.requireNonNull(getCommand("ticket")).setExecutor(ticketCommand);
|
||||||
Objects.requireNonNull(getCommand("ticket")).setTabCompleter(ticketCommand);
|
Objects.requireNonNull(getCommand("ticket")).setTabCompleter(ticketCommand);
|
||||||
|
|
||||||
// Events registrieren
|
|
||||||
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
|
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
|
||||||
getServer().getPluginManager().registerEvents(ticketGUI, 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);
|
int archiveIntervalH = getConfig().getInt("auto-archive-interval-hours", 24);
|
||||||
if (archiveIntervalH > 0) {
|
if (archiveIntervalH > 0) {
|
||||||
long ticks = archiveIntervalH * 60L * 60L * 20L; // Stunden → Ticks
|
long ticks = archiveIntervalH * 60L * 60L * 20L;
|
||||||
getServer().getScheduler().runTaskTimer(this, () -> {
|
getServer().getScheduler().runTaskTimer(this, () -> {
|
||||||
int archived = databaseManager.archiveClosedTickets();
|
int archived = databaseManager.archiveClosedTickets();
|
||||||
if (archived > 0) {
|
if (archived > 0) {
|
||||||
getLogger().info("Automatische Archivierung: " + archived + " Tickets archiviert.");
|
getLogger().info("Automatische Archivierung: " + archived + " Tickets archiviert.");
|
||||||
if (isDebug()) getLogger().info("[DEBUG] Archivierung ausgeführt, " + archived + " Tickets verschoben.");
|
|
||||||
}
|
}
|
||||||
}, ticks, ticks);
|
}, ticks, ticks);
|
||||||
getLogger().info("Automatische Archivierung alle " + archiveIntervalH + " Stunden aktiviert.");
|
getLogger().info("Automatische Archivierung alle " + archiveIntervalH + " Stunden aktiviert.");
|
||||||
if (isDebug()) getLogger().info("[DEBUG] Archivierungs-Timer gesetzt: alle " + archiveIntervalH + " Stunden.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getLogger().info("TicketSystem erfolgreich gestartet!");
|
getLogger().info("TicketSystem erfolgreich gestartet!");
|
||||||
@@ -97,26 +107,18 @@ public class TicketPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
if (databaseManager != null) {
|
if (databaseManager != null) databaseManager.disconnect();
|
||||||
databaseManager.disconnect();
|
|
||||||
}
|
|
||||||
getLogger().info("TicketSystem wurde deaktiviert.");
|
getLogger().info("TicketSystem wurde deaktiviert.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── Hilfsmethoden ─────────────────────────────
|
// ─────────────────────────── Hilfsmethoden ─────────────────────────────
|
||||||
|
|
||||||
/**
|
|
||||||
* Formatiert eine Nachricht aus der Config mit Prefix und Farben.
|
|
||||||
*/
|
|
||||||
public String formatMessage(String path) {
|
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);
|
String message = getConfig().getString(path, "&cNachricht nicht gefunden: " + path);
|
||||||
return prefix + color(message);
|
return prefix + color(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Konvertiert Farbcodes (&x → §x).
|
|
||||||
*/
|
|
||||||
public String color(String text) {
|
public String color(String text) {
|
||||||
return ChatColor.translateAlternateColorCodes('&', text);
|
return ChatColor.translateAlternateColorCodes('&', text);
|
||||||
}
|
}
|
||||||
@@ -127,9 +129,6 @@ public class TicketPlugin extends JavaPlugin {
|
|||||||
public DatabaseManager getDatabaseManager() { return databaseManager; }
|
public DatabaseManager getDatabaseManager() { return databaseManager; }
|
||||||
public TicketManager getTicketManager() { return ticketManager; }
|
public TicketManager getTicketManager() { return ticketManager; }
|
||||||
public TicketGUI getTicketGUI() { return ticketGUI; }
|
public TicketGUI getTicketGUI() { return ticketGUI; }
|
||||||
|
public DiscordWebhook getDiscordWebhook() { return discordWebhook; }
|
||||||
/**
|
|
||||||
* Gibt zurück, ob der Debug-Modus aktiv ist (aus config.yml)
|
|
||||||
*/
|
|
||||||
public boolean isDebug() { return debug; }
|
public boolean isDebug() { return debug; }
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,11 @@ public class UpdateChecker {
|
|||||||
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
|
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)) {
|
try (InputStream is = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId).openStream(); Scanner scann = new Scanner(is)) {
|
||||||
if (scann.hasNext()) {
|
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) {
|
} catch (IOException e) {
|
||||||
plugin.getLogger().info("Unable to check for updates: " + e.getMessage());
|
plugin.getLogger().info("Unable to check for updates: " + e.getMessage());
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package de.ticketsystem.commands;
|
package de.ticketsystem.commands;
|
||||||
|
|
||||||
import de.ticketsystem.TicketPlugin;
|
import de.ticketsystem.TicketPlugin;
|
||||||
@@ -9,19 +8,259 @@ import org.bukkit.command.CommandExecutor;
|
|||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.command.TabCompleter;
|
import org.bukkit.command.TabCompleter;
|
||||||
import org.bukkit.entity.Player;
|
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.File;
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TicketCommand implements CommandExecutor, TabCompleter {
|
public class TicketCommand implements CommandExecutor, TabCompleter {
|
||||||
// Platzhalter für Admin-Kommandos
|
|
||||||
|
private final TicketPlugin plugin;
|
||||||
|
|
||||||
|
public TicketCommand(TicketPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── /ticket create ────────────────────────────
|
||||||
|
|
||||||
|
private void handleCreate(Player player, String[] args) {
|
||||||
|
if (!player.hasPermission("ticket.create")) {
|
||||||
|
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(plugin.color("&cBenutzung: /ticket create <Beschreibung>"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (plugin.getTicketManager().hasCooldown(player.getUniqueId())) {
|
||||||
|
long remaining = plugin.getTicketManager().getRemainingCooldown(player.getUniqueId());
|
||||||
|
player.sendMessage(plugin.formatMessage("messages.cooldown")
|
||||||
|
.replace("{seconds}", String.valueOf(remaining)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
String message = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
|
||||||
|
int maxLen = plugin.getConfig().getInt("max-description-length", 100);
|
||||||
|
if (message.length() > maxLen) {
|
||||||
|
player.sendMessage(plugin.color("&cDeine Beschreibung ist zu lang! Maximal " + maxLen + " Zeichen."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ticket ticket = new Ticket(player.getUniqueId(), player.getName(), message, player.getLocation());
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
int id = plugin.getDatabaseManager().createTicket(ticket);
|
||||||
|
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)));
|
||||||
|
plugin.getTicketManager().notifyTeam(ticket); // ruft auch Discord-Webhook auf
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── /ticket list ──────────────────────────────
|
||||||
|
|
||||||
|
private void handleList(Player player) {
|
||||||
|
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () ->
|
||||||
|
plugin.getTicketGUI().openGUI(player)));
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () ->
|
||||||
|
plugin.getTicketGUI().openPlayerGUI(player)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── /ticket claim ─────────────────────────────
|
||||||
|
|
||||||
|
private void handleClaim(Player player, String[] args) {
|
||||||
|
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
|
||||||
|
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||||
|
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; }
|
||||||
|
|
||||||
|
final int ticketId = id;
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
boolean success = plugin.getDatabaseManager().claimTicket(ticketId, player.getUniqueId(), player.getName());
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
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);
|
||||||
|
if (ticket.getLocation() != null) player.teleport(ticket.getLocation());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── /ticket close ─────────────────────────────
|
||||||
|
|
||||||
|
private void handleClose(Player player, String[] args) {
|
||||||
|
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
|
||||||
|
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||||
|
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; }
|
||||||
|
|
||||||
|
String closeComment = args.length > 2
|
||||||
|
? String.join(" ", Arrays.copyOfRange(args, 2, args.length)) : "";
|
||||||
|
|
||||||
|
final int ticketId = id;
|
||||||
|
final String comment = closeComment;
|
||||||
|
final String closer = player.getName();
|
||||||
|
|
||||||
|
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 (ticket != null) {
|
||||||
|
ticket.setCloseComment(comment);
|
||||||
|
// closerName für Discord-Nachricht mitgeben
|
||||||
|
plugin.getTicketManager().notifyCreatorClosed(ticket, closer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () ->
|
||||||
|
player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── /ticket forward ───────────────────────────
|
||||||
|
|
||||||
|
private void handleForward(Player player, String[] args) {
|
||||||
|
if (!player.hasPermission("ticket.admin")) {
|
||||||
|
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||||
|
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; }
|
||||||
|
|
||||||
|
Player target = Bukkit.getPlayer(args[2]);
|
||||||
|
if (target == null || !target.isOnline()) {
|
||||||
|
player.sendMessage(plugin.color("&cSpieler &e" + args[2] + " &cist nicht online!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int ticketId = id;
|
||||||
|
final Player finalTarget = target;
|
||||||
|
final String fromName = player.getName();
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// fromName für Discord mitgeben
|
||||||
|
plugin.getTicketManager().notifyForwardedTo(ticket, fromName);
|
||||||
|
plugin.getTicketManager().notifyCreatorForwarded(ticket);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () ->
|
||||||
|
player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── /ticket reload ────────────────────────────
|
||||||
|
|
||||||
|
private void handleReload(Player player) {
|
||||||
|
if (!player.hasPermission("ticket.admin")) {
|
||||||
|
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
plugin.reloadConfig();
|
||||||
|
player.sendMessage(plugin.color("&aKonfiguration wurde neu geladen."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── /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 migrate ───────────────────────────
|
||||||
|
|
||||||
private void handleMigrate(Player player, String[] args) {
|
private void handleMigrate(Player player, String[] args) {
|
||||||
if (!player.hasPermission("ticket.admin")) {
|
if (!player.hasPermission("ticket.admin")) {
|
||||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||||
@@ -34,14 +273,9 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
int migrated = 0;
|
int migrated = 0;
|
||||||
String mode = args[1].toLowerCase();
|
String mode = args[1].toLowerCase();
|
||||||
if (mode.equals("tomysql")) {
|
if (mode.equals("tomysql")) migrated = plugin.getDatabaseManager().migrateToMySQL();
|
||||||
migrated = plugin.getDatabaseManager().migrateToMySQL();
|
else if (mode.equals("tofile")) migrated = plugin.getDatabaseManager().migrateToFile();
|
||||||
} else if (mode.equals("tofile")) {
|
else { player.sendMessage(plugin.formatMessage("messages.unknown-mode")); return; }
|
||||||
migrated = plugin.getDatabaseManager().migrateToFile();
|
|
||||||
} else {
|
|
||||||
player.sendMessage(plugin.formatMessage("messages.unknown-mode"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int finalMigrated = migrated;
|
int finalMigrated = migrated;
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
if (finalMigrated > 0) {
|
if (finalMigrated > 0) {
|
||||||
@@ -54,6 +288,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── /ticket export ────────────────────────────
|
||||||
|
|
||||||
private void handleExport(Player player, String[] args) {
|
private void handleExport(Player player, String[] args) {
|
||||||
if (!player.hasPermission("ticket.admin")) {
|
if (!player.hasPermission("ticket.admin")) {
|
||||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||||
@@ -78,6 +314,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── /ticket import ────────────────────────────
|
||||||
|
|
||||||
private void handleImport(Player player, String[] args) {
|
private void handleImport(Player player, String[] args) {
|
||||||
if (!player.hasPermission("ticket.admin")) {
|
if (!player.hasPermission("ticket.admin")) {
|
||||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||||
@@ -106,6 +344,8 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── /ticket stats ─────────────────────────────
|
||||||
|
|
||||||
private void handleStats(Player player) {
|
private void handleStats(Player player) {
|
||||||
if (!player.hasPermission("ticket.admin")) {
|
if (!player.hasPermission("ticket.admin")) {
|
||||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
||||||
@@ -114,308 +354,37 @@ public class TicketCommand implements CommandExecutor, TabCompleter {
|
|||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
var stats = plugin.getDatabaseManager().getTicketStats();
|
var stats = plugin.getDatabaseManager().getTicketStats();
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
player.sendMessage("§6--- Ticket Statistik ---");
|
player.sendMessage(plugin.color("&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(plugin.color("&eGesamt: &a" + stats.total
|
||||||
player.sendMessage("§6Top Ersteller:");
|
+ " &7| &eOffen: &a" + stats.open
|
||||||
stats.byPlayer.entrySet().stream().sorted((a,b)->b.getValue()-a.getValue()).limit(5).forEach(e ->
|
+ " &7| &eGeschlossen: &a" + stats.closed
|
||||||
player.sendMessage("§e" + e.getKey() + ": §a" + e.getValue())
|
+ " &7| &eWeitergeleitet: &a" + stats.forwarded));
|
||||||
);
|
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())));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── /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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (args.length < 2) {
|
|
||||||
player.sendMessage(plugin.color("&cBenutzung: /ticket create <Beschreibung>"));
|
|
||||||
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)));
|
|
||||||
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));
|
|
||||||
int maxLen = plugin.getConfig().getInt("max-description-length", 100);
|
|
||||||
if (message.length() > maxLen) {
|
|
||||||
player.sendMessage(plugin.color("&cDeine Beschreibung ist zu lang! Maximal " + maxLen + " Zeichen."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ticket asynchron in DB speichern
|
|
||||||
Ticket ticket = new Ticket(player.getUniqueId(), player.getName(), message, player.getLocation());
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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
|
|
||||||
plugin.getTicketManager().notifyTeam(ticket);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────── /ticket list ──────────────────────────────
|
|
||||||
|
|
||||||
private void handleList(Player player) {
|
|
||||||
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
|
|
||||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int ticketId = id;
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
|
||||||
boolean success = plugin.getDatabaseManager().claimTicket(
|
|
||||||
ticketId, player.getUniqueId(), player.getName());
|
|
||||||
|
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────── /ticket close ─────────────────────────────
|
|
||||||
|
|
||||||
private void handleClose(Player player, String[] args) {
|
|
||||||
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
|
|
||||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (args.length < 2) {
|
|
||||||
player.sendMessage(plugin.color("&cBenutzung: /ticket close <ID>"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int id;
|
|
||||||
try { id = Integer.parseInt(args[1]); }
|
|
||||||
catch (NumberFormatException e) {
|
|
||||||
player.sendMessage(plugin.color("&cUngültige ID!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────── /ticket forward ───────────────────────────
|
|
||||||
|
|
||||||
private void handleForward(Player player, String[] args) {
|
|
||||||
if (!player.hasPermission("ticket.admin")) {
|
|
||||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player target = Bukkit.getPlayer(args[2]);
|
|
||||||
if (target == null || !target.isOnline()) {
|
|
||||||
player.sendMessage(plugin.color("&cSpieler &e" + args[2] + " &cist nicht online!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int ticketId = id;
|
|
||||||
final Player finalTarget = target;
|
|
||||||
|
|
||||||
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 reload ────────────────────────────
|
|
||||||
|
|
||||||
private void handleReload(Player player) {
|
|
||||||
if (!player.hasPermission("ticket.admin")) {
|
|
||||||
player.sendMessage(plugin.formatMessage("messages.no-permission"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
plugin.reloadConfig();
|
|
||||||
player.sendMessage(plugin.color("&aKonfiguration wurde neu geladen."));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────── Tab-Completion ────────────────────────────
|
// ─────────────────────────── Tab-Completion ────────────────────────────
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> onTabComplete(CommandSender sender, Command command,
|
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
|
||||||
String label, String[] args) {
|
|
||||||
List<String> completions = new ArrayList<>();
|
List<String> completions = new ArrayList<>();
|
||||||
if (!(sender instanceof Player player)) return completions;
|
if (!(sender instanceof Player player)) return completions;
|
||||||
|
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
List<String> subs = new ArrayList<>();
|
List<String> subs = new ArrayList<>(List.of("create", "list"));
|
||||||
subs.add("create");
|
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin"))
|
||||||
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) {
|
subs.addAll(List.of("claim", "close"));
|
||||||
subs.addAll(List.of("list", "claim", "close"));
|
if (player.hasPermission("ticket.admin"))
|
||||||
}
|
subs.addAll(List.of("forward", "reload", "stats", "archive", "migrate", "export", "import"));
|
||||||
if (player.hasPermission("ticket.admin")) {
|
for (String s : subs)
|
||||||
subs.addAll(List.of("forward", "reload"));
|
|
||||||
}
|
|
||||||
for (String s : subs) {
|
|
||||||
if (s.startsWith(args[0].toLowerCase())) completions.add(s);
|
if (s.startsWith(args[0].toLowerCase())) completions.add(s);
|
||||||
}
|
|
||||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("forward")) {
|
} else if (args.length == 3 && args[0].equalsIgnoreCase("forward")) {
|
||||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
for (Player p : Bukkit.getOnlinePlayers())
|
||||||
if (p.getName().toLowerCase().startsWith(args[2].toLowerCase()))
|
if (p.getName().toLowerCase().startsWith(args[2].toLowerCase())) completions.add(p.getName());
|
||||||
completions.add(p.getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
195
src/main/java/de/ticketsystem/discord/DiscordWebhook.java
Normal file
195
src/main/java/de/ticketsystem/discord/DiscordWebhook.java
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
package de.ticketsystem.discord;
|
||||||
|
|
||||||
|
import de.ticketsystem.TicketPlugin;
|
||||||
|
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 und Timestamp.
|
||||||
|
*/
|
||||||
|
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 = plugin.getConfig().getString("discord.webhook-url", "");
|
||||||
|
if (webhookUrl.isEmpty()) return;
|
||||||
|
|
||||||
|
// Felder aus Config lesen
|
||||||
|
String title = plugin.getConfig().getString("discord.messages.new-ticket.title", "🎫 Neues Ticket erstellt");
|
||||||
|
String color = plugin.getConfig().getString("discord.messages.new-ticket.color", "3066993"); // Grün
|
||||||
|
String footer = plugin.getConfig().getString("discord.messages.new-ticket.footer", "TicketSystem");
|
||||||
|
boolean showPos = plugin.getConfig().getBoolean("discord.messages.new-ticket.show-position", true);
|
||||||
|
|
||||||
|
// JSON-Embed aufbauen
|
||||||
|
StringBuilder fields = new StringBuilder();
|
||||||
|
fields.append(field("Spieler", ticket.getCreatorName(), true));
|
||||||
|
fields.append(",");
|
||||||
|
fields.append(field("Ticket ID", "#" + ticket.getId(), true));
|
||||||
|
fields.append(",");
|
||||||
|
fields.append(field("Anliegen", ticket.getMessage(), false));
|
||||||
|
|
||||||
|
if (showPos) {
|
||||||
|
fields.append(",");
|
||||||
|
fields.append(field("Welt", ticket.getWorldName(), true));
|
||||||
|
fields.append(",");
|
||||||
|
fields.append(field("Position",
|
||||||
|
String.format("%.0f, %.0f, %.0f", ticket.getX(), ticket.getY(), ticket.getZ()), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = buildPayload(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 = plugin.getConfig().getString("discord.webhook-url", "");
|
||||||
|
if (webhookUrl.isEmpty()) return;
|
||||||
|
|
||||||
|
String title = plugin.getConfig().getString("discord.messages.ticket-closed.title", "🔒 Ticket geschlossen");
|
||||||
|
String color = plugin.getConfig().getString("discord.messages.ticket-closed.color", "15158332"); // Rot
|
||||||
|
String footer = plugin.getConfig().getString("discord.messages.ticket-closed.footer", "TicketSystem");
|
||||||
|
|
||||||
|
StringBuilder fields = new StringBuilder();
|
||||||
|
fields.append(field("Ticket ID", "#" + ticket.getId(), true));
|
||||||
|
fields.append(",");
|
||||||
|
fields.append(field("Ersteller", ticket.getCreatorName(), true));
|
||||||
|
fields.append(",");
|
||||||
|
fields.append(field("Geschlossen von", closerName, true));
|
||||||
|
if (ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty()) {
|
||||||
|
fields.append(",");
|
||||||
|
fields.append(field("Kommentar", ticket.getCloseComment(), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = buildPayload(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 = plugin.getConfig().getString("discord.webhook-url", "");
|
||||||
|
if (webhookUrl.isEmpty()) return;
|
||||||
|
|
||||||
|
String title = plugin.getConfig().getString("discord.messages.ticket-forwarded.title", "🔀 Ticket weitergeleitet");
|
||||||
|
String color = plugin.getConfig().getString("discord.messages.ticket-forwarded.color", "15105570"); // Orange
|
||||||
|
String footer = plugin.getConfig().getString("discord.messages.ticket-forwarded.footer", "TicketSystem");
|
||||||
|
|
||||||
|
StringBuilder fields = new StringBuilder();
|
||||||
|
fields.append(field("Ticket ID", "#" + ticket.getId(), true));
|
||||||
|
fields.append(",");
|
||||||
|
fields.append(field("Ersteller", ticket.getCreatorName(), true));
|
||||||
|
fields.append(",");
|
||||||
|
fields.append(field("Weitergeleitet von", fromName, true));
|
||||||
|
fields.append(",");
|
||||||
|
fields.append(field("Weitergeleitet an", ticket.getForwardedToName(), true));
|
||||||
|
|
||||||
|
String json = buildPayload(title, Integer.parseInt(color), fields.toString(), footer);
|
||||||
|
sendAsync(webhookUrl, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── Private Hilfsmethoden ─────────────────────
|
||||||
|
|
||||||
|
private boolean isEnabled() {
|
||||||
|
return plugin.getConfig().getBoolean("discord.enabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut einen einzelnen Embed-Field als JSON-String.
|
||||||
|
*/
|
||||||
|
private String field(String name, String value, boolean inline) {
|
||||||
|
// Anführungszeichen und Backslashes im Wert escapen
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
private String buildPayload(String title, int color, String fieldsJson, String footer) {
|
||||||
|
String timestamp = Instant.now().toString(); // ISO-8601
|
||||||
|
return String.format("""
|
||||||
|
{
|
||||||
|
"embeds": [{
|
||||||
|
"title": "%s",
|
||||||
|
"color": %d,
|
||||||
|
"fields": [%s],
|
||||||
|
"footer": { "text": "%s" },
|
||||||
|
"timestamp": "%s"
|
||||||
|
}]
|
||||||
|
}""",
|
||||||
|
title.replace("\"", "\\\""),
|
||||||
|
color,
|
||||||
|
fieldsJson,
|
||||||
|
footer.replace("\"", "\\\""),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 204 = No Content → Erfolg bei Discord
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,13 @@ import de.ticketsystem.TicketPlugin;
|
|||||||
import de.ticketsystem.model.Ticket;
|
import de.ticketsystem.model.Ticket;
|
||||||
import de.ticketsystem.model.TicketStatus;
|
import de.ticketsystem.model.TicketStatus;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.ChatColor;
|
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||||
import org.bukkit.inventory.Inventory;
|
import org.bukkit.inventory.Inventory;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.meta.ItemMeta;
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
@@ -17,167 +18,344 @@ import org.bukkit.inventory.meta.ItemMeta;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class TicketGUI implements Listener {
|
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"; // Admin/Supporter Übersicht
|
||||||
|
private static final String CLOSED_GUI_TITLE = "§8§lTicket-Archiv"; // Admin: Geschlossene Tickets
|
||||||
|
private static final String PLAYER_GUI_TITLE = "§8§lMeine Tickets"; // Spieler: eigene Tickets
|
||||||
|
private static final String DETAIL_GUI_TITLE = "§8§lTicket-Details"; // Admin: Detail-Ansicht
|
||||||
|
|
||||||
|
/** Permission für den Zugriff auf das Archiv */
|
||||||
|
private static final String ARCHIVE_PERMISSION = "ticket.archive";
|
||||||
|
|
||||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
||||||
|
|
||||||
private final TicketPlugin plugin;
|
private final TicketPlugin plugin;
|
||||||
|
|
||||||
// Speichert welcher Spieler welches Ticket an welchem Slot hat
|
/** Admin-Übersicht: Slot → Ticket */
|
||||||
private final Map<UUID, Map<Integer, Ticket>> playerSlotMap = new HashMap<>();
|
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: Player-UUID → Ticket */
|
||||||
|
private final Map<UUID, Ticket> detailTicketMap = new HashMap<>();
|
||||||
|
|
||||||
|
/** Wartet auf Chat-Eingabe für Close-Kommentar: Player-UUID → Ticket-ID */
|
||||||
|
private final Map<UUID, Integer> awaitingComment = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merkt, welche Spieler die Detail-Ansicht aus dem Archiv heraus geöffnet haben,
|
||||||
|
* damit der Zurück-Button wieder ins Archiv führt (statt in die Hauptübersicht).
|
||||||
|
*/
|
||||||
|
private final Set<UUID> viewingFromArchive = new HashSet<>();
|
||||||
|
|
||||||
public TicketGUI(TicketPlugin plugin) {
|
public TicketGUI(TicketPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── GUI öffnen ────────────────────────────────
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// ADMIN / SUPPORTER GUI (Feste 54 Slots mit Archiv-Button)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
public void openGUI(Player player) {
|
public void openGUI(Player player) {
|
||||||
|
// Lade nur offene/aktive Tickets
|
||||||
List<Ticket> tickets = plugin.getDatabaseManager().getTicketsByStatus(
|
List<Ticket> tickets = plugin.getDatabaseManager().getTicketsByStatus(
|
||||||
TicketStatus.OPEN, TicketStatus.CLAIMED, TicketStatus.FORWARDED);
|
TicketStatus.OPEN, TicketStatus.CLAIMED, TicketStatus.FORWARDED);
|
||||||
|
|
||||||
if (tickets.isEmpty()) {
|
// Admin GUI hat immer 54 Slots (6 Reihen) für feste Buttons
|
||||||
player.sendMessage(plugin.formatMessage("messages.no-open-tickets"));
|
Inventory inv = Bukkit.createInventory(null, 54, GUI_TITLE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
Inventory inv = Bukkit.createInventory(null, size, GUI_TITLE);
|
|
||||||
Map<Integer, Ticket> slotMap = new HashMap<>();
|
Map<Integer, Ticket> slotMap = new HashMap<>();
|
||||||
|
|
||||||
for (int i = 0; i < tickets.size() && i < 54; i++) {
|
// Tickets in die ersten 5 Reihen (0-44) füllen
|
||||||
|
for (int i = 0; i < tickets.size() && i < 45; i++) {
|
||||||
Ticket ticket = tickets.get(i);
|
Ticket ticket = tickets.get(i);
|
||||||
ItemStack item = buildTicketItem(ticket);
|
inv.setItem(i, buildAdminListItem(ticket));
|
||||||
inv.setItem(i, item);
|
|
||||||
slotMap.put(i, ticket);
|
slotMap.put(i, ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trennlinie am Ende, wenn Platz
|
// Letzte Reihe (45-53) mit Navigations-Items füllen
|
||||||
fillEmpty(inv);
|
// Archiv-Button nur anzeigen wenn der Spieler die Archiv-Permission hat
|
||||||
|
fillAdminNavigation(inv, false, player);
|
||||||
|
|
||||||
playerSlotMap.put(player.getUniqueId(), slotMap);
|
playerSlotMap.put(player.getUniqueId(), slotMap);
|
||||||
player.openInventory(inv);
|
player.openInventory(inv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── Item bauen ────────────────────────────────
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// ADMIN ARCHIV GUI (Geschlossene Tickets) – nur mit ticket.archive
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
private ItemStack buildTicketItem(Ticket ticket) {
|
public void openClosedGUI(Player player) {
|
||||||
// Material je nach Status
|
// ── Permission-Check ──────────────────────────────────────────────
|
||||||
Material mat;
|
if (!player.hasPermission(ARCHIVE_PERMISSION)) {
|
||||||
switch (ticket.getStatus()) {
|
player.sendMessage(plugin.color("&cDu hast keine Berechtigung, das Archiv zu öffnen."));
|
||||||
case OPEN -> mat = Material.PAPER;
|
return;
|
||||||
case CLAIMED -> mat = Material.YELLOW_DYE;
|
|
||||||
case FORWARDED -> mat = Material.ORANGE_DYE;
|
|
||||||
default -> mat = Material.PAPER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemStack item = new ItemStack(mat);
|
// Lade nur geschlossene Tickets
|
||||||
ItemMeta meta = item.getItemMeta();
|
List<Ticket> tickets = plugin.getDatabaseManager().getTicketsByStatus(TicketStatus.CLOSED);
|
||||||
if (meta == null) return item;
|
|
||||||
|
|
||||||
// Display-Name
|
Inventory inv = Bukkit.createInventory(null, 54, CLOSED_GUI_TITLE);
|
||||||
meta.setDisplayName("§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
Map<Integer, Ticket> slotMap = new HashMap<>();
|
||||||
|
|
||||||
// Lore aufbauen
|
for (int i = 0; i < tickets.size() && i < 45; i++) {
|
||||||
List<String> lore = new ArrayList<>();
|
Ticket ticket = tickets.get(i);
|
||||||
lore.add("§8§m ");
|
inv.setItem(i, buildAdminListItem(ticket));
|
||||||
lore.add("§7Ersteller: §e" + ticket.getCreatorName());
|
slotMap.put(i, ticket);
|
||||||
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 (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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lore.add("§8§m ");
|
// Navigation (Zurück-Button statt Archiv-Button)
|
||||||
|
fillAdminNavigation(inv, true, player);
|
||||||
|
|
||||||
|
playerClosedSlotMap.put(player.getUniqueId(), slotMap);
|
||||||
|
player.openInventory(inv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// SPIELER-GUI (Filtert 'playerDeleted' Tickets)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public void openPlayerGUI(Player player) {
|
||||||
|
List<Ticket> all = plugin.getDatabaseManager().getTicketsByStatus(
|
||||||
|
TicketStatus.OPEN, TicketStatus.CLAIMED, TicketStatus.FORWARDED, TicketStatus.CLOSED);
|
||||||
|
|
||||||
|
List<Ticket> tickets = new ArrayList<>();
|
||||||
|
for (Ticket t : all) {
|
||||||
|
// Verstecke Tickets, die der Spieler als gelöscht markiert hat
|
||||||
|
if (t.getCreatorUUID().equals(player.getUniqueId()) && !t.isPlayerDeleted()) {
|
||||||
|
tickets.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tickets.isEmpty()) {
|
||||||
|
player.sendMessage(plugin.color("&aDu hast aktuell keine Tickets."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = calcSize(tickets.size());
|
||||||
|
Inventory inv = Bukkit.createInventory(null, size, PLAYER_GUI_TITLE);
|
||||||
|
Map<Integer, Ticket> slotMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < tickets.size() && i < 54; i++) {
|
||||||
|
Ticket ticket = tickets.get(i);
|
||||||
|
inv.setItem(i, buildPlayerTicketItem(ticket));
|
||||||
|
slotMap.put(i, ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
fillEmpty(inv);
|
||||||
|
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 (nur wenn OPEN) / Permanent löschen (wenn CLOSED + ticket.archive) / Grau
|
||||||
if (ticket.getStatus() == TicketStatus.OPEN) {
|
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)) {
|
||||||
|
// ── NEU: Löschen-Button nur für Archiv-berechtigte Spieler ──
|
||||||
|
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 {
|
} 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);
|
// Slot 14: Schließen
|
||||||
item.setItemMeta(meta);
|
if (ticket.getStatus() != TicketStatus.CLOSED) {
|
||||||
return item;
|
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.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillEmpty(Inventory inv) {
|
// Slot 16: Zurück
|
||||||
ItemStack glass = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
inv.setItem(16, buildActionItem(
|
||||||
ItemMeta meta = glass.getItemMeta();
|
Material.ARROW,
|
||||||
if (meta != null) { meta.setDisplayName(" "); glass.setItemMeta(meta); }
|
"§7§lZurück",
|
||||||
for (int i = 0; i < inv.getSize(); i++) {
|
List.of("§7Zurück zur Ticket-Übersicht.")));
|
||||||
if (inv.getItem(i) == null) inv.setItem(i, glass);
|
|
||||||
}
|
fillEmpty(inv);
|
||||||
|
detailTicketMap.put(player.getUniqueId(), ticket);
|
||||||
|
player.openInventory(inv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── Klick-Event ───────────────────────────────
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// CLICK-EVENTS
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onInventoryClick(InventoryClickEvent event) {
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
if (!(event.getWhoClicked() instanceof Player player)) return;
|
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);
|
event.setCancelled(true);
|
||||||
|
|
||||||
|
int slot = event.getRawSlot();
|
||||||
|
if (slot < 0) return;
|
||||||
|
|
||||||
|
// ── Admin Haupt-Übersicht ──────────────────────────────────────────────
|
||||||
|
if (title.equals(GUI_TITLE)) {
|
||||||
|
// Klick auf die Truhe (Archiv-Button) in Slot 49
|
||||||
|
if (slot == 49) {
|
||||||
|
// ── Permission-Check beim Klick ──
|
||||||
|
if (!player.hasPermission(ARCHIVE_PERMISSION)) {
|
||||||
|
player.sendMessage(plugin.color("&cDu hast keine Berechtigung, das Archiv zu öffnen."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openClosedGUI(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Klick auf ein Ticket
|
||||||
Map<Integer, Ticket> slotMap = playerSlotMap.get(player.getUniqueId());
|
Map<Integer, Ticket> slotMap = playerSlotMap.get(player.getUniqueId());
|
||||||
if (slotMap == null) return;
|
if (slotMap == null) return;
|
||||||
|
Ticket ticket = slotMap.get(slot);
|
||||||
|
if (ticket != null) {
|
||||||
|
viewingFromArchive.remove(player.getUniqueId()); // Kommt aus Hauptübersicht
|
||||||
|
player.closeInventory();
|
||||||
|
openTicketDetailAsync(player, ticket);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int slot = event.getRawSlot();
|
// ── Admin Archiv (Geschlossene Tickets) ─────────────────────────────────
|
||||||
|
if (title.equals(CLOSED_GUI_TITLE)) {
|
||||||
|
// Klick auf den Zurück-Pfeil (Slot 49)
|
||||||
|
if (slot == 49) {
|
||||||
|
openGUI(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Klick auf ein Ticket
|
||||||
|
Map<Integer, Ticket> slotMap = playerClosedSlotMap.get(player.getUniqueId());
|
||||||
|
if (slotMap == null) return;
|
||||||
|
Ticket ticket = slotMap.get(slot);
|
||||||
|
if (ticket != null) {
|
||||||
|
viewingFromArchive.add(player.getUniqueId()); // Kommt aus Archiv
|
||||||
|
player.closeInventory();
|
||||||
|
openTicketDetailAsync(player, ticket);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Spieler-GUI ──────────────────────────────────────────────────────
|
||||||
|
if (title.equals(PLAYER_GUI_TITLE)) {
|
||||||
|
Map<Integer, Ticket> slotMap = playerOwnSlotMap.get(player.getUniqueId());
|
||||||
|
if (slotMap == null) return;
|
||||||
Ticket ticket = slotMap.get(slot);
|
Ticket ticket = slotMap.get(slot);
|
||||||
if (ticket == null) return;
|
if (ticket == null) return;
|
||||||
|
|
||||||
player.closeInventory();
|
player.closeInventory();
|
||||||
|
|
||||||
// Asynchron aus DB neu laden (aktuelle Daten)
|
// Nur löschen wenn OFFEN oder GESCHLOSSEN
|
||||||
|
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 {
|
||||||
|
// Ticket wird bearbeitet (Claimed oder Forwarded) -> Löschen verweigern
|
||||||
|
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 -> {
|
||||||
|
// Wenn CLOSED + archive-Permission → permanent löschen, sonst claimen
|
||||||
|
if (ticket.getStatus() == TicketStatus.CLOSED && player.hasPermission(ARCHIVE_PERMISSION)) {
|
||||||
|
handleDetailPermanentDelete(player, ticket);
|
||||||
|
} else {
|
||||||
|
handleDetailClaim(player, ticket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 14 -> handleDetailClose(player, ticket);
|
||||||
|
case 16 -> {
|
||||||
|
// Zurück zur richtigen GUI je nach Herkunft
|
||||||
|
if (viewingFromArchive.remove(player.getUniqueId())) {
|
||||||
|
openClosedGUI(player);
|
||||||
|
} else {
|
||||||
|
openGUI(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────── Detail-Aktionen & Helpers ──────────────────
|
||||||
|
|
||||||
|
private void openTicketDetailAsync(Player player, Ticket currentTicket) {
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
Ticket fresh = plugin.getDatabaseManager().getTicketById(ticket.getId());
|
Ticket fresh = plugin.getDatabaseManager().getTicketById(currentTicket.getId());
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
if (fresh == null) {
|
if (fresh == null) {
|
||||||
player.sendMessage(plugin.formatMessage("messages.ticket-not-found"));
|
player.sendMessage(plugin.formatMessage("messages.ticket-not-found"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
openDetailGUI(player, fresh);
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> handleTicketClick(player, fresh));
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTicketClick(Player player, Ticket ticket) {
|
private void handleDetailTeleport(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
|
|
||||||
if (ticket.getLocation() != null) {
|
if (ticket.getLocation() != null) {
|
||||||
player.teleport(ticket.getLocation());
|
player.teleport(ticket.getLocation());
|
||||||
player.sendMessage(plugin.color("&7Du wurdest zu Ticket &e#" + ticket.getId() + " &7teleportiert."));
|
player.sendMessage(plugin.color("&7Du wurdest zu Ticket &e#" + ticket.getId() + " &7teleportiert."));
|
||||||
@@ -185,4 +363,288 @@ public class TicketGUI implements Listener {
|
|||||||
player.sendMessage(plugin.color("&cDie Welt des Tickets ist nicht geladen!"));
|
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.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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
player.sendMessage(plugin.formatMessage("messages.already-claimed"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löscht ein geschlossenes Ticket permanent aus der Datenbank.
|
||||||
|
* Nur für Spieler mit der Permission ticket.archive.
|
||||||
|
*/
|
||||||
|
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 aus der Datenbank 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 "));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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üll-Methoden ─────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Füllt die Navigationsleiste (letzte Reihe) der Admin-GUIs.
|
||||||
|
* Der Archiv-Button (Truhe) wird nur angezeigt, wenn der Spieler ticket.archive besitzt.
|
||||||
|
*/
|
||||||
|
private void fillAdminNavigation(Inventory inv, boolean isArchiveView, Player player) {
|
||||||
|
ItemStack glass = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
|
||||||
|
ItemMeta meta = glass.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.setDisplayName(" ");
|
||||||
|
glass.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Letzte Reihe (45-53) füllen
|
||||||
|
for (int i = 45; i < 54; i++) {
|
||||||
|
if (i != 49) inv.setItem(i, glass);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArchiveView) {
|
||||||
|
// Im Archiv: Zurück-Pfeil in Slot 49
|
||||||
|
inv.setItem(49, buildActionItem(
|
||||||
|
Material.ARROW,
|
||||||
|
"§7§lZurück zur Übersicht",
|
||||||
|
List.of("§7Zeigt alle offenen Tickets.")));
|
||||||
|
} else {
|
||||||
|
// In der Übersicht: Archiv-Truhe nur mit Permission
|
||||||
|
if (player.hasPermission(ARCHIVE_PERMISSION)) {
|
||||||
|
inv.setItem(49, buildActionItem(
|
||||||
|
Material.CHEST,
|
||||||
|
"§7§lGeschlossene Tickets",
|
||||||
|
List.of("§7Zeigt alle abgeschlossenen", "§7Tickets im Archiv an.")));
|
||||||
|
} else {
|
||||||
|
// Kein Archiv-Zugriff → Slot 49 bleibt Glas (kein Button)
|
||||||
|
inv.setItem(49, glass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack buildAdminListItem(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()));
|
||||||
|
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 (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());
|
||||||
|
}
|
||||||
|
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 (ticket.getStatus() == TicketStatus.CLOSED
|
||||||
|
&& ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty()) {
|
||||||
|
lore.add("§8§m ");
|
||||||
|
lore.add("§7Kommentar des Supports:");
|
||||||
|
lore.add("§f" + ticket.getCloseComment());
|
||||||
|
}
|
||||||
|
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 int calcSize(int ticketCount) {
|
||||||
|
int size = (int) Math.ceil(ticketCount / 9.0) * 9;
|
||||||
|
return Math.max(9, Math.min(54, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
package de.ticketsystem.listeners;
|
package de.ticketsystem.listeners;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.ticketsystem.TicketPlugin;
|
import de.ticketsystem.TicketPlugin;
|
||||||
|
import de.ticketsystem.model.Ticket;
|
||||||
|
import de.ticketsystem.model.TicketStatus;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
@@ -19,21 +24,56 @@ public class PlayerJoinListener implements Listener {
|
|||||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
// Nur Supporter und Admins erhalten die Join-Benachrichtigung
|
// ── Supporter/Admin: offene Tickets anzeigen ──────────────────────
|
||||||
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) return;
|
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) {
|
||||||
|
|
||||||
// Verzögerung von 2 Sekunden damit die Join-Sequenz abgeschlossen ist
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
int count = plugin.getDatabaseManager().countOpenTickets();
|
int count = plugin.getDatabaseManager().countOpenTickets();
|
||||||
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||||
String msg = plugin.formatMessage("messages.join-open-tickets")
|
String msg = plugin.formatMessage("messages.join-open-tickets")
|
||||||
.replace("{count}", String.valueOf(count));
|
.replace("{count}", String.valueOf(count));
|
||||||
player.sendMessage(msg);
|
player.sendMessage(msg);
|
||||||
player.sendMessage(plugin.color("&7» Tippe &e/ticket list &7für die Übersicht."));
|
player.sendMessage(plugin.color("&7» Tippe &e/ticket list &7für die Übersicht."));
|
||||||
}, 40L); // 40 Ticks = 2 Sekunden
|
}, 40L); // 2 Sekunden Verzögerung
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Spieler: über geschlossene Tickets mit Kommentar informieren ──
|
||||||
|
// Nur wenn der Ersteller noch nicht live benachrichtigt wurde
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
List<Ticket> closed = plugin.getDatabaseManager()
|
||||||
|
.getTicketsByStatus(TicketStatus.CLOSED);
|
||||||
|
|
||||||
|
for (Ticket t : closed) {
|
||||||
|
if (!t.getCreatorUUID().equals(player.getUniqueId())) continue;
|
||||||
|
if (t.getCloseComment() == null || t.getCloseComment().isEmpty()) continue;
|
||||||
|
|
||||||
|
// Nicht erneut senden, wenn bereits live benachrichtigt (In-Memory-Set)
|
||||||
|
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); // 1 Sekunde
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,16 +7,21 @@ import org.bukkit.Bukkit;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class TicketManager {
|
public class TicketManager {
|
||||||
|
|
||||||
private final TicketPlugin plugin;
|
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<>();
|
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) {
|
public TicketManager(TicketPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
@@ -42,55 +47,132 @@ public class TicketManager {
|
|||||||
// ─────────────────────────── Benachrichtigungen ────────────────────────
|
// ─────────────────────────── 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) {
|
public void notifyTeam(Ticket ticket) {
|
||||||
|
// Sicherheitschecks für null-Werte
|
||||||
|
String creatorName = ticket.getCreatorName() != null ? ticket.getCreatorName() : "Unbekannt";
|
||||||
|
String message = ticket.getMessage() != null ? ticket.getMessage() : "";
|
||||||
|
|
||||||
String msg = plugin.formatMessage("messages.new-ticket-notify")
|
String msg = plugin.formatMessage("messages.new-ticket-notify")
|
||||||
.replace("{player}", ticket.getCreatorName())
|
.replace("{player}", creatorName)
|
||||||
.replace("{message}", ticket.getMessage())
|
.replace("{message}", message)
|
||||||
.replace("{id}", String.valueOf(ticket.getId()));
|
.replace("{id}", String.valueOf(ticket.getId()));
|
||||||
|
|
||||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||||
if (p.hasPermission("ticket.support") || p.hasPermission("ticket.admin")) {
|
if (p.hasPermission("ticket.support") || p.hasPermission("ticket.admin")) {
|
||||||
p.sendMessage(msg);
|
p.sendMessage(msg);
|
||||||
|
|
||||||
// Klickbaren Hinweis senden (Bukkit Chat-Component)
|
|
||||||
p.sendMessage(plugin.color("&7» Klicke &e/ticket list &7um die GUI zu öffnen."));
|
p.sendMessage(plugin.color("&7» Klicke &e/ticket list &7um die GUI zu öffnen."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discord-Webhook (asynchron)
|
||||||
|
plugin.getDiscordWebhook().sendNewTicket(ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Benachrichtigt den Ersteller des Tickets, wenn es geclaimt wurde.
|
* Benachrichtigt den Ersteller, wenn sein Ticket angenommen wurde.
|
||||||
|
* --- FIX PROBLEMK 1: NIE "UNBEKANNT" ---
|
||||||
*/
|
*/
|
||||||
public void notifyCreatorClaimed(Ticket ticket) {
|
public void notifyCreatorClaimed(Ticket ticket) {
|
||||||
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
|
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
|
||||||
if (creator != null && creator.isOnline()) {
|
if (creator != null && creator.isOnline()) {
|
||||||
|
|
||||||
|
// 1. Versuch: Name aus dem Ticket-Objekt
|
||||||
|
String claimerName = ticket.getClaimerName();
|
||||||
|
|
||||||
|
// 2. Versuch: Wenn Name fehlt, aber UUID vorhanden -> Namen über Bukkit holen
|
||||||
|
if (claimerName == null && ticket.getClaimerUUID() != null) {
|
||||||
|
claimerName = Bukkit.getOfflinePlayer(ticket.getClaimerUUID()).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fallback: Falls immer noch kein Name da ist, nimm "Support" (nie "Unbekannt")
|
||||||
|
if (claimerName == null) claimerName = "Support";
|
||||||
|
|
||||||
String msg = plugin.formatMessage("messages.ticket-claimed-notify")
|
String msg = plugin.formatMessage("messages.ticket-claimed-notify")
|
||||||
.replace("{id}", String.valueOf(ticket.getId()))
|
.replace("{id}", String.valueOf(ticket.getId()))
|
||||||
.replace("{claimer}", ticket.getClaimerName());
|
.replace("{claimer}", claimerName);
|
||||||
creator.sendMessage(msg);
|
creator.sendMessage(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sendet dem weitergeleiteten Supporter eine Benachrichtigung.
|
* Benachrichtigt den Ersteller, wenn sein Ticket weitergeleitet wurde.
|
||||||
*/
|
*/
|
||||||
public void notifyForwardedTo(Ticket ticket) {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sendet dem weitergeleiteten Supporter eine Benachrichtigung
|
||||||
|
* und informiert optional Discord.
|
||||||
|
*/
|
||||||
|
public void notifyForwardedTo(Ticket ticket, String fromName) {
|
||||||
Player target = Bukkit.getPlayer(ticket.getForwardedToUUID());
|
Player target = Bukkit.getPlayer(ticket.getForwardedToUUID());
|
||||||
if (target != null && target.isOnline()) {
|
if (target != null && target.isOnline()) {
|
||||||
|
String creatorName = ticket.getCreatorName() != null ? ticket.getCreatorName() : "Unbekannt";
|
||||||
|
|
||||||
String msg = plugin.formatMessage("messages.ticket-forwarded-notify")
|
String msg = plugin.formatMessage("messages.ticket-forwarded-notify")
|
||||||
.replace("{player}", ticket.getCreatorName())
|
.replace("{player}", creatorName)
|
||||||
.replace("{id}", String.valueOf(ticket.getId()));
|
.replace("{id}", String.valueOf(ticket.getId()));
|
||||||
target.sendMessage(msg);
|
target.sendMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discord
|
||||||
|
plugin.getDiscordWebhook().sendTicketForwarded(ticket, fromName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Benachrichtigt den Ersteller, wenn sein Ticket geschlossen wurde,
|
||||||
|
* und informiert optional Discord.
|
||||||
|
*/
|
||||||
|
public void notifyCreatorClosed(Ticket ticket) {
|
||||||
|
notifyCreatorClosed(ticket, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Benachrichtigt den Ersteller, wenn sein Ticket geschlossen wurde.
|
||||||
|
*/
|
||||||
|
public void notifyCreatorClosed(Ticket ticket, String closerName) {
|
||||||
|
notifiedClosedTickets.add(ticket.getId());
|
||||||
|
|
||||||
|
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
|
||||||
|
if (creator != null && creator.isOnline()) {
|
||||||
|
String comment = (ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty())
|
||||||
|
? ticket.getCloseComment() : "";
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discord
|
||||||
|
String closer = closerName != null ? closerName : "Unbekannt";
|
||||||
|
plugin.getDiscordWebhook().sendTicketClosed(ticket, closer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft ob der Ersteller für dieses Ticket bereits über die Schließung informiert wurde.
|
||||||
|
*/
|
||||||
|
public boolean wasClosedNotificationSent(int ticketId) {
|
||||||
|
return notifiedClosedTickets.contains(ticketId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── Hilfsmethoden ─────────────────────────────
|
// ─────────────────────────── Hilfsmethoden ─────────────────────────────
|
||||||
|
|
||||||
/**
|
|
||||||
* Prüft, ob ein Spieler zu viele offene Tickets hat.
|
|
||||||
*/
|
|
||||||
public boolean hasReachedTicketLimit(UUID uuid) {
|
public boolean hasReachedTicketLimit(UUID uuid) {
|
||||||
int max = plugin.getConfig().getInt("max-open-tickets-per-player", 2);
|
int max = plugin.getConfig().getInt("max-open-tickets-per-player", 2);
|
||||||
if (max <= 0) return false;
|
if (max <= 0) return false;
|
||||||
@@ -102,10 +184,10 @@ public class TicketManager {
|
|||||||
player.sendMessage(plugin.color("&6TicketSystem &7– Befehle"));
|
player.sendMessage(plugin.color("&6TicketSystem &7– Befehle"));
|
||||||
player.sendMessage(plugin.color("&8&m "));
|
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 <Text> &7– Neues Ticket erstellen"));
|
||||||
|
player.sendMessage(plugin.color("&e/ticket list &7– Deine Tickets ansehen (GUI)"));
|
||||||
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) {
|
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 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")) {
|
if (player.hasPermission("ticket.admin")) {
|
||||||
player.sendMessage(plugin.color("&e/ticket forward <ID> <Spieler> &7– Ticket weiterleiten"));
|
player.sendMessage(plugin.color("&e/ticket forward <ID> <Spieler> &7– Ticket weiterleiten"));
|
||||||
|
|||||||
@@ -3,18 +3,25 @@ package de.ticketsystem.model;
|
|||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||||
|
import org.bukkit.configuration.serialization.SerializableAs;
|
||||||
|
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class Ticket {
|
|
||||||
|
@SerializableAs("Ticket")
|
||||||
|
public class Ticket implements ConfigurationSerializable {
|
||||||
|
|
||||||
private int id;
|
private int id;
|
||||||
private UUID creatorUUID;
|
private UUID creatorUUID;
|
||||||
private String creatorName;
|
private String creatorName;
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
// Location-Felder (werden separat gespeichert)
|
|
||||||
private String worldName;
|
private String worldName;
|
||||||
private double x, y, z;
|
private double x, y, z;
|
||||||
private float yaw, pitch;
|
private float yaw, pitch;
|
||||||
@@ -27,9 +34,15 @@ public class Ticket {
|
|||||||
private Timestamp createdAt;
|
private Timestamp createdAt;
|
||||||
private Timestamp claimedAt;
|
private Timestamp claimedAt;
|
||||||
private Timestamp closedAt;
|
private Timestamp closedAt;
|
||||||
|
private String closeComment;
|
||||||
|
|
||||||
|
// ─── NEU: Soft Delete Flag ───
|
||||||
|
private boolean playerDeleted = false;
|
||||||
|
|
||||||
|
|
||||||
public Ticket() {}
|
public Ticket() {}
|
||||||
|
|
||||||
|
|
||||||
public Ticket(UUID creatorUUID, String creatorName, String message, Location location) {
|
public Ticket(UUID creatorUUID, String creatorName, String message, Location location) {
|
||||||
this.creatorUUID = creatorUUID;
|
this.creatorUUID = creatorUUID;
|
||||||
this.creatorName = creatorName;
|
this.creatorName = creatorName;
|
||||||
@@ -44,6 +57,109 @@ public class Ticket {
|
|||||||
this.createdAt = new Timestamp(System.currentTimeMillis());
|
this.createdAt = new Timestamp(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- NEU: Konstruktor zum Laden aus der YAML (Deserialisierung) ---
|
||||||
|
public Ticket(Map<String, Object> map) {
|
||||||
|
this.id = (int) map.get("id");
|
||||||
|
|
||||||
|
// UUIDs sicher aus String konvertieren
|
||||||
|
Object creatorObj = map.get("creatorUUID");
|
||||||
|
this.creatorUUID = creatorObj instanceof UUID ? (UUID) creatorObj : UUID.fromString((String) creatorObj);
|
||||||
|
|
||||||
|
this.creatorName = (String) map.get("creatorName");
|
||||||
|
this.message = (String) map.get("message");
|
||||||
|
this.worldName = (String) map.get("world");
|
||||||
|
|
||||||
|
// Koordinaten sicher parsen
|
||||||
|
this.x = map.get("x") instanceof Double ? (Double) map.get("x") : ((Number) map.get("x")).doubleValue();
|
||||||
|
this.y = map.get("y") instanceof Double ? (Double) map.get("y") : ((Number) map.get("y")).doubleValue();
|
||||||
|
this.z = map.get("z") instanceof Double ? (Double) map.get("z") : ((Number) map.get("z")).doubleValue();
|
||||||
|
|
||||||
|
this.yaw = map.get("yaw") instanceof Float ? (Float) map.get("yaw") : ((Number) map.get("yaw")).floatValue();
|
||||||
|
this.pitch = map.get("pitch") instanceof Float ? (Float) map.get("pitch") : ((Number) map.get("pitch")).floatValue();
|
||||||
|
|
||||||
|
this.status = TicketStatus.valueOf((String) map.get("status"));
|
||||||
|
|
||||||
|
// Timestamps aus Long (Millis) wieder zu Timestamp machen
|
||||||
|
if (map.get("createdAt") != null) {
|
||||||
|
this.createdAt = new Timestamp(((Number) map.get("createdAt")).longValue());
|
||||||
|
}
|
||||||
|
if (map.get("claimedAt") != null) {
|
||||||
|
this.claimedAt = new Timestamp(((Number) map.get("claimedAt")).longValue());
|
||||||
|
}
|
||||||
|
if (map.get("closedAt") != null) {
|
||||||
|
this.closedAt = new Timestamp(((Number) map.get("closedAt")).longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeComment = (String) map.get("closeComment");
|
||||||
|
|
||||||
|
// Optionale Felder
|
||||||
|
if (map.containsKey("claimerUUID") && map.get("claimerUUID") != null) {
|
||||||
|
Object claimerObj = map.get("claimerUUID");
|
||||||
|
this.claimerUUID = claimerObj instanceof UUID ? (UUID) claimerObj : UUID.fromString((String) claimerObj);
|
||||||
|
this.claimerName = (String) map.get("claimerName");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.containsKey("forwardedToUUID") && map.get("forwardedToUUID") != null) {
|
||||||
|
Object fwdObj = map.get("forwardedToUUID");
|
||||||
|
this.forwardedToUUID = fwdObj instanceof UUID ? (UUID) fwdObj : UUID.fromString((String) fwdObj);
|
||||||
|
this.forwardedToName = (String) map.get("forwardedToName");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── NEU: Laden des Soft Delete Flags ───
|
||||||
|
if (map.containsKey("playerDeleted")) {
|
||||||
|
this.playerDeleted = (boolean) map.get("playerDeleted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- NEU: Methode zum Speichern in die YAML (Serialisierung) ---
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> serialize() {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
|
||||||
|
map.put("id", id);
|
||||||
|
// WICHTIG: UUID als String speichern, um !!java.util.UUID Tag zu vermeiden
|
||||||
|
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());
|
||||||
|
|
||||||
|
// Timestamps als Long speichern
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── NEU: Speichern des Soft Delete Flags ───
|
||||||
|
map.put("playerDeleted", playerDeleted);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- NEU: Registrierung ---
|
||||||
|
public static void register() {
|
||||||
|
ConfigurationSerialization.registerClass(Ticket.class, "Ticket");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Deine ursprüngliche getLocation Methode (beibehalten) ---
|
||||||
public Location getLocation() {
|
public Location getLocation() {
|
||||||
World world = Bukkit.getWorld(worldName);
|
World world = Bukkit.getWorld(worldName);
|
||||||
if (world == null) return null;
|
if (world == null) return null;
|
||||||
@@ -105,4 +221,11 @@ public class Ticket {
|
|||||||
|
|
||||||
public Timestamp getClosedAt() { return closedAt; }
|
public Timestamp getClosedAt() { return closedAt; }
|
||||||
public void setClosedAt(Timestamp closedAt) { this.closedAt = closedAt; }
|
public void setClosedAt(Timestamp closedAt) { this.closedAt = closedAt; }
|
||||||
|
|
||||||
|
public String getCloseComment() { return closeComment; }
|
||||||
|
public void setCloseComment(String closeComment) { this.closeComment = closeComment; }
|
||||||
|
|
||||||
|
// ─── NEU: Getter/Setter für Soft Delete ───
|
||||||
|
public boolean isPlayerDeleted() { return playerDeleted; }
|
||||||
|
public void setPlayerDeleted(boolean playerDeleted) { this.playerDeleted = playerDeleted; }
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,16 @@ max-open-tickets-per-player: 2 # Maximale offene Tickets pro Spieler (0 = unbeg
|
|||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
auto-archive-interval-hours: 24 # Intervall in Stunden (0 = aus)
|
auto-archive-interval-hours: 24 # Intervall in Stunden (0 = aus)
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# DISCORD WEBHOOK (Optional)
|
||||||
|
# ----------------------------------------------------
|
||||||
|
discord:
|
||||||
|
# Auf true setzen um Discord-Benachrichtigungen zu aktivieren
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Webhook-URL aus Discord (Kanaleinstellungen → Integrationen → Webhook erstellen)
|
||||||
|
webhook-url: ""
|
||||||
|
|
||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
# SYSTEM-NACHRICHTEN (mit &-Farbcodes)
|
# SYSTEM-NACHRICHTEN (mit &-Farbcodes)
|
||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
@@ -82,13 +92,19 @@ messages:
|
|||||||
ticket-claimed-notify: "&aDein Ticket &e#{id} &awurde von &e{claimer} &aangenommen."
|
ticket-claimed-notify: "&aDein Ticket &e#{id} &awurde von &e{claimer} &aangenommen."
|
||||||
ticket-closed: "&aTicket &e#{id} &awurde geschlossen."
|
ticket-closed: "&aTicket &e#{id} &awurde geschlossen."
|
||||||
ticket-forwarded: "&aTicket &e#{id} &awurde an &e{player} &aweitergeleitet."
|
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})"
|
||||||
|
|
||||||
|
# --- NEU: Benachrichtigungen für den Ticket-Ersteller ---
|
||||||
|
# Wird gesendet, wenn das eigene Ticket geschlossen wurde
|
||||||
|
ticket-closed-notify: "&aDein Ticket &e#{id} &awurde geschlossen."
|
||||||
|
# Wird gesendet, wenn das eigene Ticket an einen anderen Supporter weitergeleitet wurde
|
||||||
|
ticket-forwarded-creator-notify: "&eDein Ticket &6#{id} &ewurde an &b{supporter} &eweitergeleitet."
|
||||||
|
|
||||||
# --- FEHLER & HINWEISE ---
|
# --- FEHLER & HINWEISE ---
|
||||||
no-permission: "&cDu hast keine Berechtigung!"
|
no-permission: "&cDu hast keine Berechtigung!"
|
||||||
no-open-tickets: "&aAktuell gibt es keine offenen Tickets."
|
no-open-tickets: "&aAktuell gibt es keine offenen Tickets."
|
||||||
join-open-tickets: "&eEs gibt noch &6{count} &eoffene Ticket(s)!"
|
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!"
|
already-claimed: "&cDieses Ticket wurde bereits geclaimt!"
|
||||||
ticket-not-found: "&cTicket nicht gefunden!"
|
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
|
name: TicketSystem
|
||||||
version: 1.0.1
|
version: 1.0.3
|
||||||
main: de.ticketsystem.TicketPlugin
|
main: de.ticketsystem.TicketPlugin
|
||||||
api-version: 1.20
|
api-version: 1.20
|
||||||
author: M_Viper
|
author: M_Viper
|
||||||
@@ -20,6 +20,12 @@ permissions:
|
|||||||
description: Supporter kann Tickets einsehen und claimen
|
description: Supporter kann Tickets einsehen und claimen
|
||||||
default: false
|
default: false
|
||||||
|
|
||||||
|
ticket.archive:
|
||||||
|
description: Zugriff auf das Ticket-Archiv (öffnen, einsehen, permanent löschen)
|
||||||
|
default: false
|
||||||
|
|
||||||
ticket.admin:
|
ticket.admin:
|
||||||
description: Admin hat vollen Zugriff inkl. Weiterleitung und Reload
|
description: Admin hat vollen Zugriff inkl. Weiterleitung und Reload
|
||||||
default: op
|
default: op
|
||||||
|
children:
|
||||||
|
ticket.support: true
|
||||||
Reference in New Issue
Block a user