Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a91e17a097 | |||
| 12c9379797 | |||
| 535b0aa2f3 |
143
README.md
143
README.md
@@ -16,9 +16,15 @@
|
|||||||
- **Bessere Fehlerausgaben** – Alle Fehler erscheinen im Log und für Admins im Chat, inkl. Validierungs- und Speicherfehler
|
- **Bessere Fehlerausgaben** – Alle Fehler erscheinen im Log und für Admins im Chat, inkl. Validierungs- und Speicherfehler
|
||||||
- **Debug-Modus & Versionsprüfung** – Für Entwickler und Admins, erkennt veraltete config.yml automatisch
|
- **Debug-Modus & Versionsprüfung** – Für Entwickler und Admins, erkennt veraltete config.yml automatisch
|
||||||
- **Komplett anpassbar** – Nachrichten, Farben, Limits, Speicherpfade, Archiv-Intervall, Cooldowns, Rechte
|
- **Komplett anpassbar** – Nachrichten, Farben, Limits, Speicherpfade, Archiv-Intervall, Cooldowns, Rechte
|
||||||
- **Unit-Tests** – Getestete Speicher-Logik für maximale Zuverlässigkeit
|
- **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
|
||||||
- **Dynamische GUI** – Die Ticket-GUI passt sich automatisch der Ticketanzahl an (bis zu 54 Tickets pro Seite)
|
|
||||||
- **Seiten-System** – Bei sehr vielen Tickets wird automatisch geblättert
|
- **Seiten-System** – Bei sehr vielen Tickets wird automatisch geblättert
|
||||||
|
- **Kategorie-System** – Frei konfigurierbare Kategorien (Name, Farbe, Material, Aliases) in der config.yml
|
||||||
|
- **Prioritäten-System** – Vier Stufen (LOW / NORMAL / HIGH / URGENT), beim Erstellen wählbar und nachträglich via GUI oder Befehl änderbar
|
||||||
|
- **Bewertungs-System** – Spieler können nach Ticket-Schließung den Support bewerten (`good` / `bad`), Ergebnisse in `/ticket stats`
|
||||||
|
- **Kommentar-System** – Spieler und Support können Nachrichten direkt am Ticket hinterlassen
|
||||||
|
- **Offline-Benachrichtigungen** – Verpasste Kommentar-, Schließ- und Status-Benachrichtigungen werden gespeichert und beim nächsten Login angezeigt
|
||||||
|
- **Discord-Webhook** – Benachrichtigungen mit Embeds, konfigurierbarem Rollen-Ping und Kategorie/Priorität-Anzeige
|
||||||
|
- **Blacklist** – Spieler vom Ticket-System ausschließen
|
||||||
- **Performance** – Optimiert für große Server, alle Operationen laufen asynchron und ressourcenschonend
|
- **Performance** – Optimiert für große Server, alle Operationen laufen asynchron und ressourcenschonend
|
||||||
- **Support & Erweiterbarkeit** – Sauberer Code, viele Hooks für eigene Erweiterungen
|
- **Support & Erweiterbarkeit** – Sauberer Code, viele Hooks für eigene Erweiterungen
|
||||||
|
|
||||||
@@ -26,59 +32,38 @@
|
|||||||
|
|
||||||
## Installation & Setup
|
## Installation & Setup
|
||||||
|
|
||||||
1. **config.yml** anpassen (Speicherorte, Nachrichten, Limits, Farben, MySQL-Daten etc.)
|
1. **TicketSystem.jar** in den `plugins`-Ordner legen und Server starten
|
||||||
2. **TicketSystem.jar** in den plugins-Ordner legen und Server starten
|
2. **config.yml** anpassen (Speicherorte, Nachrichten, Limits, Farben, MySQL-Daten etc.)
|
||||||
3. **/ticket**-Befehle nutzen (siehe unten)
|
3. **/ticket**-Befehle nutzen (siehe unten)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Beispiel-Konfiguration
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
config-version: 1
|
|
||||||
debug: false
|
|
||||||
storage:
|
|
||||||
type: "file" # "file" oder "mysql"
|
|
||||||
data-file: "data.yml"
|
|
||||||
archive-file: "archive.yml"
|
|
||||||
mysql:
|
|
||||||
host: "localhost"
|
|
||||||
port: 3306
|
|
||||||
database: "tickets"
|
|
||||||
user: "root"
|
|
||||||
password: "password"
|
|
||||||
useSSL: false
|
|
||||||
auto-archive-interval-hours: 24
|
|
||||||
messages:
|
|
||||||
prefix: "&7[&eTicket&7]"
|
|
||||||
ticket-created: "&aDein Ticket wurde erstellt!"
|
|
||||||
error: "&cEin Fehler ist aufgetreten!"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Befehle & Rechte
|
## Befehle & Rechte
|
||||||
|
|
||||||
### Spieler-Befehle
|
### Spieler-Befehle
|
||||||
|
|
||||||
```
|
```
|
||||||
/ticket - Ticket erstellen, verwalten, Status abfragen
|
/ticket - Hilfe & Befehlsübersicht
|
||||||
/ticket create <Nachricht> - Neues Ticket erstellen
|
/ticket create [Kategorie] [Priorität] <Text> - Neues Ticket erstellen
|
||||||
/ticket list - Zeigt alle Tickets des Spielers
|
/ticket list - Eigene Tickets in der GUI anzeigen
|
||||||
/ticket close <ID> - Ticket schließen
|
/ticket comment <ID> <Nachricht> - Kommentar zu einem Ticket hinzufügen
|
||||||
|
/ticket rate <ID> <good|bad> - Abgeschlossenes Ticket bewerten
|
||||||
```
|
```
|
||||||
|
|
||||||
### Admin-Befehle
|
### Support/Admin-Befehle
|
||||||
|
|
||||||
```
|
```
|
||||||
/ticket reload - Plugin neu laden
|
/ticket claim <ID> - Ticket annehmen
|
||||||
/ticket migrate - Speicherart wechseln (Migration)
|
/ticket close <ID> [Kommentar] - Ticket schließen
|
||||||
/ticket export/import - Tickets exportieren/importieren
|
/ticket forward <ID> <Spieler> - Ticket weiterleiten
|
||||||
/ticket stats - Statistiken anzeigen
|
/ticket setpriority <ID> <low|normal|high|urgent> - Priorität eines Tickets ändern
|
||||||
/ticket archive - Tickets archivieren
|
/ticket reload - Konfiguration neu laden (inkl. Kategorien)
|
||||||
/ticket claim <ID> - Ticket übernehmen
|
/ticket stats - Statistiken anzeigen
|
||||||
/ticket forward <ID> <Spieler> - Ticket weiterleiten
|
/ticket archive - Geschlossene Tickets archivieren
|
||||||
/ticket close <ID> - Ticket schließen
|
/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
|
### Permissions
|
||||||
@@ -86,9 +71,9 @@ messages:
|
|||||||
| Permission | Beschreibung | Standard |
|
| Permission | Beschreibung | Standard |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `ticket.create` | Ticket erstellen | ✅ alle Spieler |
|
| `ticket.create` | Ticket erstellen | ✅ alle Spieler |
|
||||||
| `ticket.support` | Tickets einsehen, claimen & schließen | ❌ manuell vergeben |
|
| `ticket.support` | Tickets einsehen, claimen, schließen & Priorität ändern | ❌ manuell vergeben |
|
||||||
| `ticket.archive` | Archiv öffnen, einsehen & Tickets permanent löschen | ❌ manuell vergeben |
|
| `ticket.archive` | Archiv öffnen, einsehen & Tickets permanent löschen | ❌ manuell vergeben |
|
||||||
| `ticket.admin` | Voller Zugriff inkl. Weiterleitung & Reload (beinhaltet `ticket.support`) | OP |
|
| `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:
|
> ⚠️ **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:
|
||||||
> ```
|
> ```
|
||||||
@@ -97,23 +82,68 @@ messages:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Kategorie & Priorität beim Erstellen
|
||||||
|
|
||||||
|
Beim Erstellen eines Tickets können Kategorie und Priorität optional angegeben werden:
|
||||||
|
|
||||||
|
```
|
||||||
|
/ticket create <Text> → Kategorie: Standard, Priorität: NORMAL
|
||||||
|
/ticket create bug <Text> → Kategorie: Bug, Priorität: NORMAL
|
||||||
|
/ticket create high <Text> → Kategorie: Standard, Priorität: HIGH
|
||||||
|
/ticket create bug high <Text> → Kategorie: Bug, Priorität: HIGH
|
||||||
|
/ticket create question urgent <Text> → Kategorie: Frage, Priorität: URGENT
|
||||||
|
```
|
||||||
|
|
||||||
|
Verfügbare Prioritäten: `low`, `normal`, `high`, `urgent` (auch auf Deutsch: `niedrig`, `hoch`, `dringend`)
|
||||||
|
|
||||||
|
Kategorien und ihre Aliases sind frei in der `config.yml` konfigurierbar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Discord-Webhook
|
||||||
|
|
||||||
|
Der integrierte Discord-Webhook unterstützt:
|
||||||
|
|
||||||
|
- **Embeds** mit Kategorie und Priorität als eigene Felder
|
||||||
|
- **Rollen-Ping** (`@role`) pro Nachrichtentyp einzeln konfigurierbar
|
||||||
|
- **Drei Ereignisse:** Neues Ticket, Ticket geschlossen, Ticket weitergeleitet
|
||||||
|
|
||||||
|
Konfiguration in der `config.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
discord:
|
||||||
|
enabled: true
|
||||||
|
webhook-url: "https://discord.com/api/webhooks/..."
|
||||||
|
role-ping-id: "123456789012345678" # Discord-Rollen-ID (leer = kein Ping)
|
||||||
|
messages:
|
||||||
|
new-ticket:
|
||||||
|
role-ping: true
|
||||||
|
show-category: true
|
||||||
|
show-priority: true
|
||||||
|
ticket-closed:
|
||||||
|
enabled: true
|
||||||
|
role-ping: false
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
**Kann ich zwischen MySQL und Datei-Speicherung wechseln?**
|
**Kann ich zwischen MySQL und Datei-Speicherung wechseln?**
|
||||||
> Ja! Das Plugin migriert alle Daten automatisch und sicher.
|
> 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 automatisch 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?**
|
|
||||||
> Mit `/ticket export` und `/ticket import` – ideal für Server-Umzüge.
|
|
||||||
|
|
||||||
**Wer darf das Ticket-Archiv sehen?**
|
**Wer darf das Ticket-Archiv sehen?**
|
||||||
> Nur Spieler mit der Permission `ticket.archive`. Diese wird weder automatisch an OPs noch an Admins vergeben und muss explizit zugewiesen werden.
|
> Nur Spieler mit der Permission `ticket.archive`. Diese wird weder automatisch an OPs noch an Admins vergeben und muss explizit zugewiesen werden.
|
||||||
|
|
||||||
@@ -125,9 +155,14 @@ messages:
|
|||||||
|----------------------------------|:------------:|:-------------:|:---------------:|
|
|----------------------------------|:------------:|:-------------:|:---------------:|
|
||||||
| Speicher-Migration | ✔️ | ⚠️ | ✖️ |
|
| Speicher-Migration | ✔️ | ⚠️ | ✖️ |
|
||||||
| Automatische Backups | ✔️ | ⚠️ | ✖️ |
|
| Automatische Backups | ✔️ | ⚠️ | ✖️ |
|
||||||
| GUI | ✔️ | ⚠️ | ✖️ |
|
| GUI mit Kategorie-Materialien | ✔️ | ⚠️ | ✖️ |
|
||||||
| Archivierung | ✔️ | ⚠️ | ✖️ |
|
| Archivierung | ✔️ | ⚠️ | ✖️ |
|
||||||
| Rollenbasierter Archiv-Zugriff | ✔️ | ✖️ | ✖️ |
|
| Rollenbasierter Archiv-Zugriff | ✔️ | ✖️ | ✖️ |
|
||||||
|
| Kategorie-System (konfigurierbar)| ✔️ | ✖️ | ✖️ |
|
||||||
|
| Prioritäten-System | ✔️ | ✖️ | ✖️ |
|
||||||
|
| Offline-Benachrichtigungen | ✔️ | ✖️ | ✖️ |
|
||||||
|
| Discord-Webhook mit Rollen-Ping | ✔️ | ✖️ | ✖️ |
|
||||||
|
| Bewertungs-System | ✔️ | ✖️ | ✖️ |
|
||||||
| Update-Checker | ✔️ | ✖️ | ✖️ |
|
| Update-Checker | ✔️ | ✖️ | ✖️ |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -143,5 +178,5 @@ Wir antworten in der Regel innerhalb von 24 Stunden!
|
|||||||
|
|
||||||
## ⭐ Unterstütze das Projekt
|
## ⭐ Unterstütze das Projekt
|
||||||
|
|
||||||
Wenn TicketSystem deinen Server bereichert hat, freuen wir uns über eine **5-Sterne Bewertung auf spigotmc**!
|
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.
|
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.2</version>
|
<version>1.0.3</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>TicketSystem</name>
|
<name>TicketSystem</name>
|
||||||
|
|||||||
@@ -177,7 +177,6 @@ public class DatabaseManager {
|
|||||||
// ─────────────────────────── Tabellen erstellen ────────────────────────
|
// ─────────────────────────── Tabellen erstellen ────────────────────────
|
||||||
|
|
||||||
private void createTables() {
|
private void createTables() {
|
||||||
// close_comment ist jetzt von Anfang an in der CREATE-Anweisung enthalten
|
|
||||||
String sql = """
|
String sql = """
|
||||||
CREATE TABLE IF NOT EXISTS tickets (
|
CREATE TABLE IF NOT EXISTS tickets (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
@@ -198,7 +197,8 @@ public class DatabaseManager {
|
|||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
claimed_at TIMESTAMP NULL,
|
claimed_at TIMESTAMP NULL,
|
||||||
closed_at TIMESTAMP NULL,
|
closed_at TIMESTAMP NULL,
|
||||||
close_comment VARCHAR(500) NULL
|
close_comment VARCHAR(500) NULL,
|
||||||
|
player_deleted BOOLEAN DEFAULT FALSE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
""";
|
""";
|
||||||
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
|
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
|
||||||
@@ -210,11 +210,9 @@ public class DatabaseManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Ergänzt fehlende Spalten in bestehenden Datenbanken automatisch.
|
* Ergänzt fehlende Spalten in bestehenden Datenbanken automatisch.
|
||||||
* Wichtig für Server, die das Plugin bereits installiert hatten bevor
|
|
||||||
* close_comment existierte.
|
|
||||||
*/
|
*/
|
||||||
private void ensureColumns() {
|
private void ensureColumns() {
|
||||||
// close_comment hinzufügen, falls nicht vorhanden
|
// close_comment hinzufügen
|
||||||
String checkSql = """
|
String checkSql = """
|
||||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
@@ -225,20 +223,34 @@ public class DatabaseManager {
|
|||||||
Statement stmt = conn.createStatement()) {
|
Statement stmt = conn.createStatement()) {
|
||||||
ResultSet rs = stmt.executeQuery(checkSql);
|
ResultSet rs = stmt.executeQuery(checkSql);
|
||||||
if (rs.next() && rs.getInt(1) == 0) {
|
if (rs.next() && rs.getInt(1) == 0) {
|
||||||
// Spalte existiert nicht → hinzufügen
|
|
||||||
stmt.execute("ALTER TABLE tickets ADD COLUMN close_comment VARCHAR(500) NULL");
|
stmt.execute("ALTER TABLE tickets ADD COLUMN close_comment VARCHAR(500) NULL");
|
||||||
plugin.getLogger().info("[TicketSystem] Spalte 'close_comment' wurde zur Datenbank hinzugefügt.");
|
plugin.getLogger().info("[TicketSystem] Spalte 'close_comment' wurde zur Datenbank hinzugefügt.");
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
plugin.getLogger().log(Level.SEVERE, "Fehler bei ensureColumns(): " + e.getMessage(), e);
|
plugin.getLogger().log(Level.SEVERE, "Fehler bei ensureColumns(): " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// player_deleted Spalte prüfen
|
||||||
|
String checkSqlDel = """
|
||||||
|
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'tickets'
|
||||||
|
AND COLUMN_NAME = 'player_deleted'
|
||||||
|
""";
|
||||||
|
try (Connection conn = getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
ResultSet rs = stmt.executeQuery(checkSqlDel);
|
||||||
|
if (rs.next() && rs.getInt(1) == 0) {
|
||||||
|
stmt.execute("ALTER TABLE tickets ADD COLUMN player_deleted BOOLEAN DEFAULT FALSE");
|
||||||
|
plugin.getLogger().info("[TicketSystem] Spalte 'player_deleted' wurde hinzugefügt.");
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().log(Level.SEVERE, "Fehler bei ensureColumns(player_deleted): " + e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── CRUD ──────────────────────────────────────
|
// ─────────────────────────── CRUD ──────────────────────────────────────
|
||||||
|
|
||||||
/**
|
|
||||||
* Speichert ein neues Ticket in der DB und gibt die generierte ID zurück.
|
|
||||||
*/
|
|
||||||
public int createTicket(Ticket ticket) {
|
public int createTicket(Ticket ticket) {
|
||||||
if (useMySQL) {
|
if (useMySQL) {
|
||||||
String sql = """
|
String sql = """
|
||||||
@@ -282,13 +294,14 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ─── FIX: player_deleted wird beim Claimen zurückgesetzt, damit der Spieler
|
||||||
* Claimt ein Ticket (Status → CLAIMED).
|
// sein Ticket wieder sieht, sobald ein Supporter es annimmt. ───────
|
||||||
*/
|
|
||||||
public boolean claimTicket(int ticketId, UUID claimerUUID, String claimerName) {
|
public boolean claimTicket(int ticketId, UUID claimerUUID, String claimerName) {
|
||||||
if (useMySQL) {
|
if (useMySQL) {
|
||||||
String sql = """
|
String sql = """
|
||||||
UPDATE tickets SET status = 'CLAIMED', claimer_uuid = ?, claimer_name = ?, claimed_at = NOW()
|
UPDATE tickets
|
||||||
|
SET status = 'CLAIMED', claimer_uuid = ?, claimer_name = ?,
|
||||||
|
claimed_at = NOW(), player_deleted = FALSE
|
||||||
WHERE id = ? AND status = 'OPEN'
|
WHERE id = ? AND status = 'OPEN'
|
||||||
""";
|
""";
|
||||||
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
@@ -307,6 +320,7 @@ public class DatabaseManager {
|
|||||||
t.setClaimerUUID(claimerUUID);
|
t.setClaimerUUID(claimerUUID);
|
||||||
t.setClaimerName(claimerName);
|
t.setClaimerName(claimerName);
|
||||||
t.setClaimedAt(new Timestamp(System.currentTimeMillis()));
|
t.setClaimedAt(new Timestamp(System.currentTimeMillis()));
|
||||||
|
t.setPlayerDeleted(false); // FIX: Sichtbarkeit für den Spieler wiederherstellen
|
||||||
dataConfig.set("tickets." + ticketId, t);
|
dataConfig.set("tickets." + ticketId, t);
|
||||||
try {
|
try {
|
||||||
dataConfig.save(dataFile);
|
dataConfig.save(dataFile);
|
||||||
@@ -318,9 +332,6 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Schließt ein Ticket (Status → CLOSED).
|
|
||||||
*/
|
|
||||||
public boolean closeTicket(int ticketId, String closeComment) {
|
public boolean closeTicket(int ticketId, String closeComment) {
|
||||||
if (useMySQL) {
|
if (useMySQL) {
|
||||||
String sql = """
|
String sql = """
|
||||||
@@ -352,9 +363,36 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ─── Soft Delete Methode ────────────────────────────────────────────────
|
||||||
* Löscht ein Ticket anhand der ID.
|
public boolean markAsPlayerDeleted(int id) {
|
||||||
*/
|
if (useMySQL) {
|
||||||
|
String sql = "UPDATE tickets SET player_deleted = TRUE WHERE id = ?";
|
||||||
|
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
|
ps.setInt(1, id);
|
||||||
|
return ps.executeUpdate() > 0;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.getLogger().log(Level.SEVERE, "Fehler beim Markieren als gelöscht: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (dataConfig.contains("tickets." + id)) {
|
||||||
|
Ticket t = (Ticket) dataConfig.get("tickets." + id);
|
||||||
|
if (t != null) {
|
||||||
|
t.setPlayerDeleted(true);
|
||||||
|
dataConfig.set("tickets." + id, t);
|
||||||
|
try {
|
||||||
|
dataConfig.save(dataFile);
|
||||||
|
backupDataFile();
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("Fehler beim Speichern (Soft Delete): " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean deleteTicket(int id) {
|
public boolean deleteTicket(int id) {
|
||||||
if (useMySQL) {
|
if (useMySQL) {
|
||||||
String sql = "DELETE FROM tickets WHERE id = ?";
|
String sql = "DELETE FROM tickets WHERE id = ?";
|
||||||
@@ -384,13 +422,14 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ─── FIX: player_deleted wird beim Weiterleiten zurückgesetzt, damit der
|
||||||
* Leitet ein Ticket an einen anderen Supporter weiter (Status → FORWARDED).
|
// Spieler sein Ticket wieder sieht, sobald es weitergeleitet wird. ──
|
||||||
*/
|
|
||||||
public boolean forwardTicket(int ticketId, UUID toUUID, String toName) {
|
public boolean forwardTicket(int ticketId, UUID toUUID, String toName) {
|
||||||
if (useMySQL) {
|
if (useMySQL) {
|
||||||
String sql = """
|
String sql = """
|
||||||
UPDATE tickets SET status = 'FORWARDED', forwarded_to_uuid = ?, forwarded_to_name = ?
|
UPDATE tickets
|
||||||
|
SET status = 'FORWARDED', forwarded_to_uuid = ?, forwarded_to_name = ?,
|
||||||
|
player_deleted = FALSE
|
||||||
WHERE id = ? AND status != 'CLOSED'
|
WHERE id = ? AND status != 'CLOSED'
|
||||||
""";
|
""";
|
||||||
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
@@ -408,6 +447,7 @@ public class DatabaseManager {
|
|||||||
t.setStatus(TicketStatus.FORWARDED);
|
t.setStatus(TicketStatus.FORWARDED);
|
||||||
t.setForwardedToUUID(toUUID);
|
t.setForwardedToUUID(toUUID);
|
||||||
t.setForwardedToName(toName);
|
t.setForwardedToName(toName);
|
||||||
|
t.setPlayerDeleted(false); // FIX: Sichtbarkeit für den Spieler wiederherstellen
|
||||||
dataConfig.set("tickets." + ticketId, t);
|
dataConfig.set("tickets." + ticketId, t);
|
||||||
try {
|
try {
|
||||||
dataConfig.save(dataFile);
|
dataConfig.save(dataFile);
|
||||||
@@ -421,9 +461,6 @@ public class DatabaseManager {
|
|||||||
|
|
||||||
// ─────────────────────────── Abfragen ──────────────────────────────────
|
// ─────────────────────────── Abfragen ──────────────────────────────────
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt alle Tickets mit einem bestimmten Status zurück.
|
|
||||||
*/
|
|
||||||
public List<Ticket> getTicketsByStatus(TicketStatus... statuses) {
|
public List<Ticket> getTicketsByStatus(TicketStatus... statuses) {
|
||||||
List<Ticket> list = new ArrayList<>();
|
List<Ticket> list = new ArrayList<>();
|
||||||
if (statuses.length == 0) return list;
|
if (statuses.length == 0) return list;
|
||||||
@@ -452,9 +489,6 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt alle Tickets zurück (alle Status).
|
|
||||||
*/
|
|
||||||
public List<Ticket> getAllTickets() {
|
public List<Ticket> getAllTickets() {
|
||||||
List<Ticket> list = new ArrayList<>();
|
List<Ticket> list = new ArrayList<>();
|
||||||
if (useMySQL) {
|
if (useMySQL) {
|
||||||
@@ -475,9 +509,6 @@ public class DatabaseManager {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt ein einzelnes Ticket anhand der ID zurück.
|
|
||||||
*/
|
|
||||||
public Ticket getTicketById(int id) {
|
public Ticket getTicketById(int id) {
|
||||||
if (useMySQL) {
|
if (useMySQL) {
|
||||||
String sql = "SELECT * FROM tickets WHERE id = ?";
|
String sql = "SELECT * FROM tickets WHERE id = ?";
|
||||||
@@ -497,9 +528,6 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Anzahl offener Tickets (OPEN) – für Join-Benachrichtigung.
|
|
||||||
*/
|
|
||||||
public int countOpenTickets() {
|
public int countOpenTickets() {
|
||||||
if (useMySQL) {
|
if (useMySQL) {
|
||||||
String sql = "SELECT COUNT(*) FROM tickets WHERE status = 'OPEN'";
|
String sql = "SELECT COUNT(*) FROM tickets WHERE status = 'OPEN'";
|
||||||
@@ -522,9 +550,6 @@ public class DatabaseManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Anzahl offener Tickets eines bestimmten Spielers.
|
|
||||||
*/
|
|
||||||
public int countOpenTicketsByPlayer(UUID uuid) {
|
public int countOpenTicketsByPlayer(UUID uuid) {
|
||||||
if (useMySQL) {
|
if (useMySQL) {
|
||||||
String sql = "SELECT COUNT(*) FROM tickets WHERE creator_uuid = ? AND status IN ('OPEN', 'CLAIMED', 'FORWARDED')";
|
String sql = "SELECT COUNT(*) FROM tickets WHERE creator_uuid = ? AND status IN ('OPEN', 'CLAIMED', 'FORWARDED')";
|
||||||
@@ -553,9 +578,6 @@ public class DatabaseManager {
|
|||||||
|
|
||||||
// ─────────────────────────── Archivierung ──────────────────────────────
|
// ─────────────────────────── Archivierung ──────────────────────────────
|
||||||
|
|
||||||
/**
|
|
||||||
* Archiviert alle geschlossenen Tickets in eine separate Datei.
|
|
||||||
*/
|
|
||||||
public int archiveClosedTickets() {
|
public int archiveClosedTickets() {
|
||||||
List<Ticket> all = getAllTickets();
|
List<Ticket> all = getAllTickets();
|
||||||
List<Ticket> toArchive = new ArrayList<>();
|
List<Ticket> toArchive = new ArrayList<>();
|
||||||
@@ -705,11 +727,6 @@ public class DatabaseManager {
|
|||||||
|
|
||||||
// ─────────────────────────── Mapping ───────────────────────────────────
|
// ─────────────────────────── Mapping ───────────────────────────────────
|
||||||
|
|
||||||
/**
|
|
||||||
* Liest eine Zeile aus dem ResultSet und erstellt ein Ticket-Objekt.
|
|
||||||
* close_comment wird mit try-catch abgesichert, damit ältere Datenbanken
|
|
||||||
* ohne diese Spalte nicht abstürzen.
|
|
||||||
*/
|
|
||||||
private Ticket mapRow(ResultSet rs) throws SQLException {
|
private Ticket mapRow(ResultSet rs) throws SQLException {
|
||||||
Ticket t = new Ticket();
|
Ticket t = new Ticket();
|
||||||
t.setId(rs.getInt("id"));
|
t.setId(rs.getInt("id"));
|
||||||
@@ -727,15 +744,10 @@ public class DatabaseManager {
|
|||||||
t.setClaimedAt(rs.getTimestamp("claimed_at"));
|
t.setClaimedAt(rs.getTimestamp("claimed_at"));
|
||||||
t.setClosedAt(rs.getTimestamp("closed_at"));
|
t.setClosedAt(rs.getTimestamp("closed_at"));
|
||||||
|
|
||||||
// ── BUGFIX: close_comment mit try-catch absichern ──────────────────
|
|
||||||
// Wenn die Spalte in einer alten DB noch nicht existiert, wird der
|
|
||||||
// Fehler ignoriert statt die gesamte Ticket-Liste leer zu lassen.
|
|
||||||
try {
|
try {
|
||||||
String closeComment = rs.getString("close_comment");
|
String closeComment = rs.getString("close_comment");
|
||||||
if (closeComment != null) t.setCloseComment(closeComment);
|
if (closeComment != null) t.setCloseComment(closeComment);
|
||||||
} catch (SQLException ignored) {
|
} catch (SQLException ignored) { }
|
||||||
// Spalte existiert noch nicht – ensureColumns() ergänzt sie beim nächsten Start
|
|
||||||
}
|
|
||||||
|
|
||||||
String claimerUUID = rs.getString("claimer_uuid");
|
String claimerUUID = rs.getString("claimer_uuid");
|
||||||
if (claimerUUID != null) {
|
if (claimerUUID != null) {
|
||||||
@@ -747,6 +759,10 @@ public class DatabaseManager {
|
|||||||
t.setForwardedToUUID(UUID.fromString(fwdUUID));
|
t.setForwardedToUUID(UUID.fromString(fwdUUID));
|
||||||
t.setForwardedToName(rs.getString("forwarded_to_name"));
|
t.setForwardedToName(rs.getString("forwarded_to_name"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mapping des Soft Delete Flags
|
||||||
|
t.setPlayerDeleted(rs.getBoolean("player_deleted"));
|
||||||
|
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,6 +789,7 @@ public class DatabaseManager {
|
|||||||
if (t.getForwardedToUUID() != null) obj.put("forwardedToUUID", t.getForwardedToUUID().toString());
|
if (t.getForwardedToUUID() != null) obj.put("forwardedToUUID", t.getForwardedToUUID().toString());
|
||||||
if (t.getForwardedToName() != null) obj.put("forwardedToName", t.getForwardedToName());
|
if (t.getForwardedToName() != null) obj.put("forwardedToName", t.getForwardedToName());
|
||||||
if (t.getCloseComment() != null) obj.put("closeComment", t.getCloseComment());
|
if (t.getCloseComment() != null) obj.put("closeComment", t.getCloseComment());
|
||||||
|
obj.put("playerDeleted", t.isPlayerDeleted());
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,6 +815,7 @@ public class DatabaseManager {
|
|||||||
if (obj.get("forwardedToUUID") != null) t.setForwardedToUUID(UUID.fromString((String) obj.get("forwardedToUUID")));
|
if (obj.get("forwardedToUUID") != null) t.setForwardedToUUID(UUID.fromString((String) obj.get("forwardedToUUID")));
|
||||||
if (obj.get("forwardedToName") != null) t.setForwardedToName((String) obj.get("forwardedToName"));
|
if (obj.get("forwardedToName") != null) t.setForwardedToName((String) obj.get("forwardedToName"));
|
||||||
if (obj.get("closeComment") != null) t.setCloseComment((String) obj.get("closeComment"));
|
if (obj.get("closeComment") != null) t.setCloseComment((String) obj.get("closeComment"));
|
||||||
|
if (obj.containsKey("playerDeleted")) t.setPlayerDeleted((Boolean) obj.get("playerDeleted"));
|
||||||
return t;
|
return t;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (plugin != null) plugin.getLogger().severe("Fehler beim Parsen eines Tickets: " + e.getMessage());
|
if (plugin != null) plugin.getLogger().severe("Fehler beim Parsen eines Tickets: " + e.getMessage());
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ 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 {
|
||||||
@@ -27,9 +29,13 @@ public class TicketGUI implements Listener {
|
|||||||
// ─────────────────────────── Titel-Konstanten ──────────────────────────
|
// ─────────────────────────── Titel-Konstanten ──────────────────────────
|
||||||
|
|
||||||
private static final String GUI_TITLE = "§8§lTicket-Übersicht"; // Admin/Supporter Übersicht
|
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 PLAYER_GUI_TITLE = "§8§lMeine Tickets"; // Spieler: eigene Tickets
|
||||||
private static final String DETAIL_GUI_TITLE = "§8§lTicket-Details"; // Admin: Detail-Ansicht
|
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;
|
||||||
@@ -37,6 +43,9 @@ public class TicketGUI implements Listener {
|
|||||||
/** Admin-Übersicht: Slot → Ticket */
|
/** 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 */
|
/** Spieler-GUI: Slot → Ticket */
|
||||||
private final Map<UUID, Map<Integer, Ticket>> playerOwnSlotMap = new HashMap<>();
|
private final Map<UUID, Map<Integer, Ticket>> playerOwnSlotMap = new HashMap<>();
|
||||||
|
|
||||||
@@ -46,40 +55,76 @@ public class TicketGUI implements Listener {
|
|||||||
/** Wartet auf Chat-Eingabe für Close-Kommentar: Player-UUID → Ticket-ID */
|
/** Wartet auf Chat-Eingabe für Close-Kommentar: Player-UUID → Ticket-ID */
|
||||||
private final Map<UUID, Integer> awaitingComment = new HashMap<>();
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
// ADMIN / SUPPORTER GUI (Übersicht aller Tickets)
|
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
int size = calcSize(tickets.size());
|
|
||||||
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);
|
||||||
inv.setItem(i, buildAdminListItem(ticket));
|
inv.setItem(i, buildAdminListItem(ticket));
|
||||||
slotMap.put(i, ticket);
|
slotMap.put(i, ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
fillEmpty(inv);
|
// Letzte Reihe (45-53) mit Navigations-Items füllen
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
// SPIELER-GUI (nur eigene Tickets, mit Lösch-Option bei OPEN)
|
// ADMIN ARCHIV GUI (Geschlossene Tickets) – nur mit ticket.archive
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
public void openClosedGUI(Player player) {
|
||||||
|
// ── Permission-Check ──────────────────────────────────────────────
|
||||||
|
if (!player.hasPermission(ARCHIVE_PERMISSION)) {
|
||||||
|
player.sendMessage(plugin.color("&cDu hast keine Berechtigung, das Archiv zu öffnen."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lade nur geschlossene Tickets
|
||||||
|
List<Ticket> tickets = plugin.getDatabaseManager().getTicketsByStatus(TicketStatus.CLOSED);
|
||||||
|
|
||||||
|
Inventory inv = Bukkit.createInventory(null, 54, CLOSED_GUI_TITLE);
|
||||||
|
Map<Integer, Ticket> slotMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < tickets.size() && i < 45; i++) {
|
||||||
|
Ticket ticket = tickets.get(i);
|
||||||
|
inv.setItem(i, buildAdminListItem(ticket));
|
||||||
|
slotMap.put(i, ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
public void openPlayerGUI(Player player) {
|
||||||
@@ -88,7 +133,10 @@ public class TicketGUI implements Listener {
|
|||||||
|
|
||||||
List<Ticket> tickets = new ArrayList<>();
|
List<Ticket> tickets = new ArrayList<>();
|
||||||
for (Ticket t : all) {
|
for (Ticket t : all) {
|
||||||
if (t.getCreatorUUID().equals(player.getUniqueId())) tickets.add(t);
|
// Verstecke Tickets, die der Spieler als gelöscht markiert hat
|
||||||
|
if (t.getCreatorUUID().equals(player.getUniqueId()) && !t.isPlayerDeleted()) {
|
||||||
|
tickets.add(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tickets.isEmpty()) {
|
if (tickets.isEmpty()) {
|
||||||
@@ -112,27 +160,37 @@ public class TicketGUI implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
// ADMIN DETAIL-GUI (Aktionen für ein einzelnes Ticket)
|
// ADMIN DETAIL-GUI
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
public void openDetailGUI(Player player, Ticket ticket) {
|
public void openDetailGUI(Player player, Ticket ticket) {
|
||||||
Inventory inv = Bukkit.createInventory(null, 27, DETAIL_GUI_TITLE);
|
Inventory inv = Bukkit.createInventory(null, 27, DETAIL_GUI_TITLE);
|
||||||
|
|
||||||
// Slot 4: Ticket-Info (Mitte oben)
|
// Slot 4: Ticket-Info
|
||||||
inv.setItem(4, buildDetailInfoItem(ticket));
|
inv.setItem(4, buildDetailInfoItem(ticket));
|
||||||
|
|
||||||
// Slot 10: Teleportieren (immer verfügbar)
|
// Slot 10: Teleportieren
|
||||||
inv.setItem(10, buildActionItem(
|
inv.setItem(10, buildActionItem(
|
||||||
Material.ENDER_PEARL,
|
Material.ENDER_PEARL,
|
||||||
"§b§lTeleportieren",
|
"§b§lTeleportieren",
|
||||||
List.of("§7Teleportiert dich zur", "§7Position des Tickets.")));
|
List.of("§7Teleportiert dich zur", "§7Position des Tickets.")));
|
||||||
|
|
||||||
// Slot 12: Claimen (nur wenn OPEN), sonst grauer Platzhalter
|
// Slot 12: Claimen (nur wenn OPEN) / Permanent löschen (wenn CLOSED + ticket.archive) / Grau
|
||||||
if (ticket.getStatus() == TicketStatus.OPEN) {
|
if (ticket.getStatus() == TicketStatus.OPEN) {
|
||||||
inv.setItem(12, buildActionItem(
|
inv.setItem(12, buildActionItem(
|
||||||
Material.LIME_WOOL,
|
Material.LIME_WOOL,
|
||||||
"§a§lTicket annehmen",
|
"§a§lTicket annehmen",
|
||||||
List.of("§7Nimmt dieses Ticket an", "§7und markiert es als bearbeitet.")));
|
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 {
|
||||||
inv.setItem(12, buildActionItem(
|
inv.setItem(12, buildActionItem(
|
||||||
Material.GRAY_WOOL,
|
Material.GRAY_WOOL,
|
||||||
@@ -140,18 +198,12 @@ public class TicketGUI implements Listener {
|
|||||||
List.of("§7Dieses Ticket wurde bereits", "§7angenommen.")));
|
List.of("§7Dieses Ticket wurde bereits", "§7angenommen.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slot 14: Schließen — für OPEN, CLAIMED und FORWARDED; grauer Block wenn bereits CLOSED
|
// Slot 14: Schließen
|
||||||
if (ticket.getStatus() != TicketStatus.CLOSED) {
|
if (ticket.getStatus() != TicketStatus.CLOSED) {
|
||||||
inv.setItem(14, buildActionItem(
|
inv.setItem(14, buildActionItem(
|
||||||
Material.RED_WOOL,
|
Material.RED_WOOL,
|
||||||
"§c§lTicket schließen",
|
"§c§lTicket schließen",
|
||||||
List.of(
|
List.of("§7Schließt das Ticket.", "§8§m ", "§eKlick für Kommentar-Eingabe.")));
|
||||||
"§7Schließt das Ticket.",
|
|
||||||
"§8§m ",
|
|
||||||
"§eNach dem Klick kannst du im",
|
|
||||||
"§eChat einen Kommentar eingeben.",
|
|
||||||
"§7Tippe §c- §7für keinen Kommentar.",
|
|
||||||
"§7Tippe §ccancel §7zum Abbrechen.")));
|
|
||||||
} else {
|
} else {
|
||||||
inv.setItem(14, buildActionItem(
|
inv.setItem(14, buildActionItem(
|
||||||
Material.GRAY_WOOL,
|
Material.GRAY_WOOL,
|
||||||
@@ -159,7 +211,7 @@ public class TicketGUI implements Listener {
|
|||||||
List.of("§7Dieses Ticket ist bereits", "§7geschlossen.")));
|
List.of("§7Dieses Ticket ist bereits", "§7geschlossen.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slot 16: Zurück zur Übersicht
|
// Slot 16: Zurück
|
||||||
inv.setItem(16, buildActionItem(
|
inv.setItem(16, buildActionItem(
|
||||||
Material.ARROW,
|
Material.ARROW,
|
||||||
"§7§lZurück",
|
"§7§lZurück",
|
||||||
@@ -179,36 +231,58 @@ public class TicketGUI implements Listener {
|
|||||||
if (!(event.getWhoClicked() instanceof Player player)) return;
|
if (!(event.getWhoClicked() instanceof Player player)) return;
|
||||||
String title = event.getView().getTitle();
|
String title = event.getView().getTitle();
|
||||||
|
|
||||||
if (!title.equals(GUI_TITLE) && !title.equals(PLAYER_GUI_TITLE) && !title.equals(DETAIL_GUI_TITLE)) return;
|
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();
|
int slot = event.getRawSlot();
|
||||||
if (slot < 0) return;
|
if (slot < 0) return;
|
||||||
|
|
||||||
// ── Admin-Übersicht ──────────────────────────────────────────────
|
// ── Admin Haupt-Übersicht ──────────────────────────────────────────────
|
||||||
if (title.equals(GUI_TITLE)) {
|
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);
|
Ticket ticket = slotMap.get(slot);
|
||||||
if (ticket == null) return;
|
if (ticket != null) {
|
||||||
|
viewingFromArchive.remove(player.getUniqueId()); // Kommt aus Hauptübersicht
|
||||||
player.closeInventory();
|
player.closeInventory();
|
||||||
|
openTicketDetailAsync(player, ticket);
|
||||||
// Frische Daten aus DB holen, dann Detail-GUI öffnen
|
}
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
|
||||||
Ticket fresh = plugin.getDatabaseManager().getTicketById(ticket.getId());
|
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
|
||||||
if (fresh == null) {
|
|
||||||
player.sendMessage(plugin.formatMessage("messages.ticket-not-found"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
openDetailGUI(player, fresh);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Spieler-GUI ──────────────────────────────────────────────────
|
// ── 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)) {
|
if (title.equals(PLAYER_GUI_TITLE)) {
|
||||||
Map<Integer, Ticket> slotMap = playerOwnSlotMap.get(player.getUniqueId());
|
Map<Integer, Ticket> slotMap = playerOwnSlotMap.get(player.getUniqueId());
|
||||||
if (slotMap == null) return;
|
if (slotMap == null) return;
|
||||||
@@ -217,28 +291,26 @@ public class TicketGUI implements Listener {
|
|||||||
|
|
||||||
player.closeInventory();
|
player.closeInventory();
|
||||||
|
|
||||||
if (ticket.getStatus() == TicketStatus.OPEN) {
|
// Nur löschen wenn OFFEN oder GESCHLOSSEN
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
if (ticket.getStatus() == TicketStatus.OPEN || ticket.getStatus() == TicketStatus.CLOSED) {
|
||||||
boolean deleted = plugin.getDatabaseManager().deleteTicket(ticket.getId());
|
boolean success = plugin.getDatabaseManager().markAsPlayerDeleted(ticket.getId());
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
|
||||||
if (deleted) {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
player.sendMessage(plugin.color(
|
if (success) {
|
||||||
"&aDein Ticket &e#" + ticket.getId() + " &awurde gelöscht."));
|
player.sendMessage(plugin.color("&aDein Ticket &e#" + ticket.getId() + " &awurde aus deiner Übersicht entfernt."));
|
||||||
openPlayerGUI(player);
|
openPlayerGUI(player);
|
||||||
} else {
|
} else {
|
||||||
player.sendMessage(plugin.color("&cFehler beim Löschen des Tickets."));
|
player.sendMessage(plugin.color("&cFehler beim Entfernen des Tickets."));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
player.sendMessage(plugin.color(
|
// Ticket wird bearbeitet (Claimed oder Forwarded) -> Löschen verweigern
|
||||||
"&cDieses Ticket kann nicht mehr gelöscht werden, " +
|
player.sendMessage(plugin.color("&cDu kannst dieses Ticket nicht löschen, da es bereits von einem Supporter bearbeitet wird."));
|
||||||
"da es bereits angenommen oder geschlossen wurde."));
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Admin Detail-GUI ─────────────────────────────────────────────
|
// ── Admin Detail-GUI ─────────────────────────────────────────────────
|
||||||
if (title.equals(DETAIL_GUI_TITLE)) {
|
if (title.equals(DETAIL_GUI_TITLE)) {
|
||||||
Ticket ticket = detailTicketMap.get(player.getUniqueId());
|
Ticket ticket = detailTicketMap.get(player.getUniqueId());
|
||||||
if (ticket == null) return;
|
if (ticket == null) return;
|
||||||
@@ -247,21 +319,46 @@ public class TicketGUI implements Listener {
|
|||||||
|
|
||||||
switch (slot) {
|
switch (slot) {
|
||||||
case 10 -> handleDetailTeleport(player, ticket);
|
case 10 -> handleDetailTeleport(player, ticket);
|
||||||
case 12 -> handleDetailClaim(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 14 -> handleDetailClose(player, ticket);
|
||||||
case 16 -> openGUI(player);
|
case 16 -> {
|
||||||
// Glasscheiben und andere Slots → nichts tun
|
// Zurück zur richtigen GUI je nach Herkunft
|
||||||
|
if (viewingFromArchive.remove(player.getUniqueId())) {
|
||||||
|
openClosedGUI(player);
|
||||||
|
} else {
|
||||||
|
openGUI(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── Detail-Aktionen ───────────────────────────
|
// ─────────────────────────── Detail-Aktionen & Helpers ──────────────────
|
||||||
|
|
||||||
|
private void openTicketDetailAsync(Player player, Ticket currentTicket) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
Ticket fresh = plugin.getDatabaseManager().getTicketById(currentTicket.getId());
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
if (fresh == null) {
|
||||||
|
player.sendMessage(plugin.formatMessage("messages.ticket-not-found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openDetailGUI(player, fresh);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void handleDetailTeleport(Player player, Ticket ticket) {
|
private void handleDetailTeleport(Player player, Ticket ticket) {
|
||||||
if (ticket.getLocation() != null) {
|
if (ticket.getLocation() != null) {
|
||||||
player.teleport(ticket.getLocation());
|
player.teleport(ticket.getLocation());
|
||||||
player.sendMessage(plugin.color(
|
player.sendMessage(plugin.color("&7Du wurdest zu Ticket &e#" + ticket.getId() + " &7teleportiert."));
|
||||||
"&7Du wurdest zu Ticket &e#" + ticket.getId() + " &7teleportiert."));
|
|
||||||
} else {
|
} else {
|
||||||
player.sendMessage(plugin.color("&cDie Welt des Tickets ist nicht geladen!"));
|
player.sendMessage(plugin.color("&cDie Welt des Tickets ist nicht geladen!"));
|
||||||
}
|
}
|
||||||
@@ -272,32 +369,26 @@ public class TicketGUI implements Listener {
|
|||||||
player.sendMessage(plugin.formatMessage("messages.already-claimed"));
|
player.sendMessage(plugin.formatMessage("messages.already-claimed"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
boolean success = plugin.getDatabaseManager().claimTicket(
|
boolean success = plugin.getDatabaseManager().claimTicket(ticket.getId(), player.getUniqueId(), player.getName());
|
||||||
ticket.getId(), player.getUniqueId(), player.getName());
|
|
||||||
|
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
if (success) {
|
if (success) {
|
||||||
player.sendMessage(plugin.formatMessage("messages.ticket-claimed")
|
player.sendMessage(plugin.formatMessage("messages.ticket-claimed")
|
||||||
.replace("{id}", String.valueOf(ticket.getId()))
|
.replace("{id}", String.valueOf(ticket.getId()))
|
||||||
.replace("{player}", ticket.getCreatorName()));
|
.replace("{player}", ticket.getCreatorName()));
|
||||||
|
|
||||||
plugin.getTicketManager().notifyCreatorClaimed(ticket);
|
ticket.setClaimerUUID(player.getUniqueId());
|
||||||
|
ticket.setClaimerName(player.getName());
|
||||||
|
|
||||||
|
plugin.getTicketManager().notifyCreatorClaimed(ticket);
|
||||||
if (ticket.getLocation() != null) player.teleport(ticket.getLocation());
|
if (ticket.getLocation() != null) player.teleport(ticket.getLocation());
|
||||||
|
|
||||||
// ── BUGFIX: Detail-GUI mit frischen DB-Daten neu öffnen ──
|
|
||||||
// Dadurch verschwindet der Claim-Button und der Schließen-Button
|
|
||||||
// ist sofort korrekt sichtbar, ohne dass der Admin die GUI
|
|
||||||
// erst schließen und neu öffnen muss.
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
Ticket fresh = plugin.getDatabaseManager().getTicketById(ticket.getId());
|
Ticket fresh = plugin.getDatabaseManager().getTicketById(ticket.getId());
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
if (fresh != null) openDetailGUI(player, fresh);
|
if (fresh != null) openDetailGUI(player, fresh);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
player.sendMessage(plugin.formatMessage("messages.already-claimed"));
|
player.sendMessage(plugin.formatMessage("messages.already-claimed"));
|
||||||
}
|
}
|
||||||
@@ -305,76 +396,125 @@ public class TicketGUI implements Listener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
private void handleDetailClose(Player player, Ticket ticket) {
|
||||||
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
||||||
player.sendMessage(plugin.color("&cDieses Ticket ist bereits geschlossen."));
|
player.sendMessage(plugin.color("&cDieses Ticket ist bereits geschlossen."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitingComment.put(player.getUniqueId(), ticket.getId());
|
awaitingComment.put(player.getUniqueId(), ticket.getId());
|
||||||
player.sendMessage(plugin.color("&8&m "));
|
player.sendMessage(plugin.color("&8&m "));
|
||||||
player.sendMessage(plugin.color("&6Ticket #" + ticket.getId() + " schließen"));
|
player.sendMessage(plugin.color("&6Ticket #" + ticket.getId() + " schließen"));
|
||||||
player.sendMessage(plugin.color("&7Gib einen Kommentar für den Spieler ein."));
|
player.sendMessage(plugin.color("&7Gib einen Kommentar ein (&e- &7für keinen)."));
|
||||||
player.sendMessage(plugin.color("&7Kein Kommentar? Tippe: &e-"));
|
player.sendMessage(plugin.color("&7Abbrechen mit &ccancel"));
|
||||||
player.sendMessage(plugin.color("&7Abbrechen? Tippe: &ccancel"));
|
|
||||||
player.sendMessage(plugin.color("&8&m "));
|
player.sendMessage(plugin.color("&8&m "));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── Chat-Listener (Kommentar-Eingabe) ─────────
|
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOWEST)
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
if (!awaitingComment.containsKey(player.getUniqueId())) return;
|
if (!awaitingComment.containsKey(player.getUniqueId())) return;
|
||||||
|
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
|
|
||||||
int ticketId = awaitingComment.remove(player.getUniqueId());
|
int ticketId = awaitingComment.remove(player.getUniqueId());
|
||||||
String input = event.getMessage().trim();
|
String input = event.getMessage().trim();
|
||||||
|
|
||||||
if (input.equalsIgnoreCase("cancel")) {
|
if (input.equalsIgnoreCase("cancel")) {
|
||||||
Bukkit.getScheduler().runTask(plugin, () ->
|
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.color("&cAbgebrochen.")));
|
||||||
player.sendMessage(plugin.color("&cSchließen abgebrochen.")));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// "-" = bewusst kein Kommentar
|
|
||||||
final String comment = input.equals("-") ? "" : input;
|
final String comment = input.equals("-") ? "" : input;
|
||||||
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
boolean success = plugin.getDatabaseManager().closeTicket(ticketId, comment);
|
boolean success = plugin.getDatabaseManager().closeTicket(ticketId, comment);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
|
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
|
||||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
player.sendMessage(plugin.formatMessage("messages.ticket-closed")
|
player.sendMessage(plugin.formatMessage("messages.ticket-closed").replace("{id}", String.valueOf(ticketId)));
|
||||||
.replace("{id}", String.valueOf(ticketId)));
|
if (!comment.isEmpty()) player.sendMessage(plugin.color("&7Kommentar: &f" + comment));
|
||||||
|
|
||||||
if (!comment.isEmpty()) {
|
|
||||||
player.sendMessage(plugin.color("&7Kommentar gespeichert: &f" + comment));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ticket != null) {
|
if (ticket != null) {
|
||||||
ticket.setCloseComment(comment);
|
ticket.setCloseComment(comment);
|
||||||
plugin.getTicketManager().notifyCreatorClosed(ticket);
|
plugin.getTicketManager().notifyCreatorClosed(ticket, player.getName());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
Bukkit.getScheduler().runTask(plugin, () ->
|
|
||||||
player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ─────────────────────────── Item-Builder & Füll-Methoden ─────────────
|
||||||
// ITEM-BUILDER
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
/**
|
||||||
|
* 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) {
|
private ItemStack buildAdminListItem(Ticket ticket) {
|
||||||
Material mat = switch (ticket.getStatus()) {
|
Material mat = switch (ticket.getStatus()) {
|
||||||
case OPEN -> Material.PAPER;
|
case OPEN -> Material.PAPER;
|
||||||
case CLAIMED -> Material.YELLOW_DYE;
|
case CLAIMED -> Material.YELLOW_DYE;
|
||||||
case FORWARDED -> Material.ORANGE_DYE;
|
case FORWARDED -> Material.ORANGE_DYE;
|
||||||
default -> Material.PAPER;
|
case CLOSED -> Material.GRAY_DYE;
|
||||||
};
|
};
|
||||||
|
|
||||||
ItemStack item = new ItemStack(mat);
|
ItemStack item = new ItemStack(mat);
|
||||||
@@ -382,20 +522,19 @@ public class TicketGUI implements Listener {
|
|||||||
if (meta == null) return item;
|
if (meta == null) return item;
|
||||||
|
|
||||||
meta.setDisplayName("§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
meta.setDisplayName("§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
||||||
|
|
||||||
List<String> lore = new ArrayList<>();
|
List<String> lore = new ArrayList<>();
|
||||||
lore.add("§8§m ");
|
lore.add("§8§m ");
|
||||||
lore.add("§7Ersteller: §e" + ticket.getCreatorName());
|
lore.add("§7Ersteller: §e" + ticket.getCreatorName());
|
||||||
lore.add("§7Anliegen: §f" + ticket.getMessage());
|
lore.add("§7Anliegen: §f" + ticket.getMessage());
|
||||||
lore.add("§7Erstellt: §e" + DATE_FORMAT.format(ticket.getCreatedAt()));
|
lore.add("§7Erstellt: §e" + DATE_FORMAT.format(ticket.getCreatedAt()));
|
||||||
lore.add("§7Welt: §e" + ticket.getWorldName());
|
if (ticket.getStatus() == TicketStatus.CLOSED && ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty()) {
|
||||||
lore.add(String.format("§7Position: §e%.0f, %.0f, %.0f", ticket.getX(), ticket.getY(), ticket.getZ()));
|
lore.add("§7Kommentar: §f" + ticket.getCloseComment());
|
||||||
if (ticket.getClaimerName() != null)
|
}
|
||||||
lore.add("§7Angenommen: §a" + ticket.getClaimerName());
|
if (ticket.isPlayerDeleted()) {
|
||||||
if (ticket.getForwardedToName() != null)
|
lore.add("§cSpieler hat Ticket gelöscht.");
|
||||||
lore.add("§7Weitergeleitet an: §6" + ticket.getForwardedToName());
|
}
|
||||||
lore.add("§8§m ");
|
lore.add("§8§m ");
|
||||||
lore.add("§e§l» KLICKEN für Details & Aktionen");
|
lore.add("§e§l» KLICKEN für Details");
|
||||||
|
|
||||||
meta.setLore(lore);
|
meta.setLore(lore);
|
||||||
item.setItemMeta(meta);
|
item.setItemMeta(meta);
|
||||||
@@ -415,7 +554,6 @@ public class TicketGUI implements Listener {
|
|||||||
if (meta == null) return item;
|
if (meta == null) return item;
|
||||||
|
|
||||||
meta.setDisplayName("§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
meta.setDisplayName("§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
||||||
|
|
||||||
List<String> lore = new ArrayList<>();
|
List<String> lore = new ArrayList<>();
|
||||||
lore.add("§8§m ");
|
lore.add("§8§m ");
|
||||||
lore.add("§7Ersteller: §e" + ticket.getCreatorName());
|
lore.add("§7Ersteller: §e" + ticket.getCreatorName());
|
||||||
@@ -429,8 +567,6 @@ public class TicketGUI implements Listener {
|
|||||||
if (ticket.getClaimedAt() != null)
|
if (ticket.getClaimedAt() != null)
|
||||||
lore.add("§7Angenommen am: §a" + DATE_FORMAT.format(ticket.getClaimedAt()));
|
lore.add("§7Angenommen am: §a" + DATE_FORMAT.format(ticket.getClaimedAt()));
|
||||||
}
|
}
|
||||||
if (ticket.getForwardedToName() != null)
|
|
||||||
lore.add("§7Weitergeleitet an: §6" + ticket.getForwardedToName());
|
|
||||||
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
if (ticket.getStatus() == TicketStatus.CLOSED) {
|
||||||
if (ticket.getClosedAt() != null)
|
if (ticket.getClosedAt() != null)
|
||||||
lore.add("§7Geschlossen am: §c" + DATE_FORMAT.format(ticket.getClosedAt()));
|
lore.add("§7Geschlossen am: §c" + DATE_FORMAT.format(ticket.getClosedAt()));
|
||||||
@@ -438,7 +574,6 @@ public class TicketGUI implements Listener {
|
|||||||
lore.add("§7Kommentar: §f" + ticket.getCloseComment());
|
lore.add("§7Kommentar: §f" + ticket.getCloseComment());
|
||||||
}
|
}
|
||||||
lore.add("§8§m ");
|
lore.add("§8§m ");
|
||||||
|
|
||||||
meta.setLore(lore);
|
meta.setLore(lore);
|
||||||
item.setItemMeta(meta);
|
item.setItemMeta(meta);
|
||||||
return item;
|
return item;
|
||||||
@@ -457,17 +592,12 @@ public class TicketGUI implements Listener {
|
|||||||
if (meta == null) return item;
|
if (meta == null) return item;
|
||||||
|
|
||||||
meta.setDisplayName("§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
meta.setDisplayName("§6§lTicket #" + ticket.getId() + " §r" + ticket.getStatus().getColored());
|
||||||
|
|
||||||
List<String> lore = new ArrayList<>();
|
List<String> lore = new ArrayList<>();
|
||||||
lore.add("§8§m ");
|
lore.add("§8§m ");
|
||||||
lore.add("§7Anliegen: §f" + ticket.getMessage());
|
lore.add("§7Anliegen: §f" + ticket.getMessage());
|
||||||
lore.add("§7Erstellt: §e" + DATE_FORMAT.format(ticket.getCreatedAt()));
|
lore.add("§7Erstellt: §e" + DATE_FORMAT.format(ticket.getCreatedAt()));
|
||||||
lore.add("§7Welt: §e" + ticket.getWorldName());
|
lore.add("§7Welt: §e" + ticket.getWorldName());
|
||||||
lore.add(String.format("§7Position: §e%.0f, %.0f, %.0f", ticket.getX(), ticket.getY(), ticket.getZ()));
|
lore.add(String.format("§7Position: §e%.0f, %.0f, %.0f", ticket.getX(), ticket.getY(), ticket.getZ()));
|
||||||
if (ticket.getStatus() == TicketStatus.CLAIMED && ticket.getClaimerName() != null)
|
|
||||||
lore.add("§7Angenommen von: §a" + ticket.getClaimerName());
|
|
||||||
if (ticket.getStatus() == TicketStatus.FORWARDED && ticket.getForwardedToName() != null)
|
|
||||||
lore.add("§7Bearbeiter: §6" + ticket.getForwardedToName());
|
|
||||||
if (ticket.getStatus() == TicketStatus.CLOSED
|
if (ticket.getStatus() == TicketStatus.CLOSED
|
||||||
&& ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty()) {
|
&& ticket.getCloseComment() != null && !ticket.getCloseComment().isEmpty()) {
|
||||||
lore.add("§8§m ");
|
lore.add("§8§m ");
|
||||||
@@ -475,14 +605,17 @@ public class TicketGUI implements Listener {
|
|||||||
lore.add("§f" + ticket.getCloseComment());
|
lore.add("§f" + ticket.getCloseComment());
|
||||||
}
|
}
|
||||||
lore.add("§8§m ");
|
lore.add("§8§m ");
|
||||||
switch (ticket.getStatus()) {
|
|
||||||
case OPEN -> { lore.add("§c§l» KLICKEN zum Löschen");
|
|
||||||
lore.add("§7Nur möglich solange noch nicht angenommen."); }
|
|
||||||
case CLOSED -> lore.add("§8» Dieses Ticket ist abgeschlossen.");
|
|
||||||
default -> { lore.add("§e» Ticket wird bearbeitet...");
|
|
||||||
lore.add("§7Kann nicht mehr gelöscht werden."); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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);
|
meta.setLore(lore);
|
||||||
item.setItemMeta(meta);
|
item.setItemMeta(meta);
|
||||||
return item;
|
return item;
|
||||||
@@ -498,8 +631,6 @@ public class TicketGUI implements Listener {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────── Hilfsmethoden ─────────────────────────────
|
|
||||||
|
|
||||||
private int calcSize(int ticketCount) {
|
private int calcSize(int ticketCount) {
|
||||||
int size = (int) Math.ceil(ticketCount / 9.0) * 9;
|
int size = (int) Math.ceil(ticketCount / 9.0) * 9;
|
||||||
return Math.max(9, Math.min(54, size));
|
return Math.max(9, Math.min(54, size));
|
||||||
|
|||||||
@@ -51,9 +51,13 @@ public class TicketManager {
|
|||||||
* und sendet optional eine Discord-Webhook-Nachricht.
|
* 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()) {
|
||||||
@@ -63,19 +67,32 @@ public class TicketManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discord-Webhook (asynchron, kein Einfluss auf Server-Performance)
|
// Discord-Webhook (asynchron)
|
||||||
plugin.getDiscordWebhook().sendNewTicket(ticket);
|
plugin.getDiscordWebhook().sendNewTicket(ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Benachrichtigt den Ersteller, wenn sein Ticket angenommen 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,8 +118,10 @@ public class TicketManager {
|
|||||||
public void notifyForwardedTo(Ticket ticket, String fromName) {
|
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);
|
||||||
}
|
}
|
||||||
@@ -121,7 +140,6 @@ public class TicketManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Benachrichtigt den Ersteller, wenn sein Ticket geschlossen wurde.
|
* Benachrichtigt den Ersteller, wenn sein Ticket geschlossen wurde.
|
||||||
* @param closerName Name des Admins/Supporters der es geschlossen hat (für Discord, kann null sein)
|
|
||||||
*/
|
*/
|
||||||
public void notifyCreatorClosed(Ticket ticket, String closerName) {
|
public void notifyCreatorClosed(Ticket ticket, String closerName) {
|
||||||
notifiedClosedTickets.add(ticket.getId());
|
notifiedClosedTickets.add(ticket.getId());
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ public class Ticket implements ConfigurationSerializable {
|
|||||||
private Timestamp closedAt;
|
private Timestamp closedAt;
|
||||||
private String closeComment;
|
private String closeComment;
|
||||||
|
|
||||||
|
// ─── NEU: Soft Delete Flag ───
|
||||||
|
private boolean playerDeleted = false;
|
||||||
|
|
||||||
|
|
||||||
public Ticket() {}
|
public Ticket() {}
|
||||||
|
|
||||||
@@ -101,6 +104,11 @@ public class Ticket implements ConfigurationSerializable {
|
|||||||
this.forwardedToUUID = fwdObj instanceof UUID ? (UUID) fwdObj : UUID.fromString((String) fwdObj);
|
this.forwardedToUUID = fwdObj instanceof UUID ? (UUID) fwdObj : UUID.fromString((String) fwdObj);
|
||||||
this.forwardedToName = (String) map.get("forwardedToName");
|
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) ---
|
// --- NEU: Methode zum Speichern in die YAML (Serialisierung) ---
|
||||||
@@ -140,6 +148,9 @@ public class Ticket implements ConfigurationSerializable {
|
|||||||
map.put("forwardedToName", forwardedToName);
|
map.put("forwardedToName", forwardedToName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── NEU: Speichern des Soft Delete Flags ───
|
||||||
|
map.put("playerDeleted", playerDeleted);
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,4 +224,8 @@ public class Ticket implements ConfigurationSerializable {
|
|||||||
|
|
||||||
public String getCloseComment() { return closeComment; }
|
public String getCloseComment() { return closeComment; }
|
||||||
public void setCloseComment(String closeComment) { this.closeComment = 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; }
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
name: TicketSystem
|
name: TicketSystem
|
||||||
version: 1.0.2
|
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