Upload via GUI (19 Dateien)
This commit is contained in:
@@ -1,124 +1,557 @@
|
|||||||
<div align="center">
|
# 🎄 Adventskalender – Event-Kalender & Geburtstage für Minecraft
|
||||||
|
|
||||||
# ❄️ Minecraft Adventskalender ❄️
|
Ein Plugin für deinen Minecraft-Server, das Spielern jeden Tag ein Geschenk gibt.
|
||||||
|
Es hat **drei Kalender** und ein **Geburtstags-Geschenk**:
|
||||||
|
|
||||||
*Bringe festliche Stimmung auf deinen Server mit einem hochgradig anpassbaren System*
|
| Was | Wann | Türchen |
|
||||||
|
|---|---|---|
|
||||||
|
| 🎄 **Adventskalender** | ab 1. Dezember | 24 |
|
||||||
|
| 🎃 **Halloween-Kalender** | ab 1. Oktober | 31 |
|
||||||
|
| 🐰 **Oster-Kalender** | 13 Tage vor Ostersonntag bis Ostersonntag | 14 |
|
||||||
|
| 🎂 **Geburtstags-Geschenk** | am Geburtstag des Spielers + 2 Tage danach | 1 pro Jahr |
|
||||||
|
|
||||||

|
Du musst **nichts programmieren**. Plugin hineinkopieren, Server starten, fertig.
|
||||||

|
Alles Weitere (Belohnungen, Texte, Zeiten) kannst du in normalen Textdateien ändern.
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Über Minecraft Adventskalender
|
## Inhaltsverzeichnis
|
||||||
|
|
||||||
Das **Minecraft Adventskalender Plugin** ist die ultimative Lösung für deinen Server im Dezember! Es bietet volle Kontrolle über Belohnungen, Sprache und Funktionen. Dank des neuen klickbaren Update-Checkers und dem erweiterten Admin-Test-System war die Verwaltung eines Events noch nie so einfach. Perfekt für Community-Events, um die Spielerbindung in der Weihnachtszeit zu stärken!
|
1. [Was brauche ich?](#1-was-brauche-ich)
|
||||||
|
2. [Installation in 5 Schritten](#2-installation-in-5-schritten)
|
||||||
|
3. [So benutzen Spieler das Plugin](#3-so-benutzen-spieler-das-plugin)
|
||||||
|
4. [Befehle für Admins](#4-befehle-für-admins)
|
||||||
|
5. [Rechte (Permissions)](#5-rechte-permissions)
|
||||||
|
6. [Die Dateien des Plugins](#6-die-dateien-des-plugins)
|
||||||
|
7. [Belohnungen ändern](#7-belohnungen-ändern)
|
||||||
|
8. [Kalender ein- und ausschalten oder anpassen](#8-kalender-ein--und-ausschalten-oder-anpassen)
|
||||||
|
9. [Geburtstage einstellen](#9-geburtstage-einstellen)
|
||||||
|
10. [Texte und Sprache ändern](#10-texte-und-sprache-ändern)
|
||||||
|
11. [Alles testen, ohne auf den Dezember zu warten](#11-alles-testen-ohne-auf-den-dezember-zu-warten)
|
||||||
|
12. [Jeder darf nur einmal – oder der ganze Server nur einmal?](#12-jeder-darf-nur-einmal--oder-der-ganze-server-nur-einmal)
|
||||||
|
13. [Mehrere Server mit einer Datenbank (MySQL)](#13-mehrere-server-mit-einer-datenbank-mysql)
|
||||||
|
14. [Platzhalter für Scoreboards, Hologramme & Co.](#14-platzhalter-für-scoreboards-hologramme--co)
|
||||||
|
15. [Plugin aktualisieren](#15-plugin-aktualisieren)
|
||||||
|
16. [Probleme und ihre Lösung](#16-probleme-und-ihre-lösung)
|
||||||
|
17. [Häufige Fragen](#17-häufige-fragen)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ✨ Exklusive Features
|
## 1. Was brauche ich?
|
||||||
|
|
||||||
- **Vollständig Anpassbare Belohnungen:** Definiere für jeden Tag individuelle Items mit Menge, Name, Lore und Verzauberungen.
|
- Einen Minecraft-Server mit **Spigot** oder **Paper** (oder einem Fork davon) ab **Version 1.20**.
|
||||||
- **Smart Update-Checker:** Erhalte beim Joinen als Admin eine dezente, **klickbare Benachrichtigung**, falls eine neue Version verfügbar ist (erscheint verzögert nach 2 Sek.).
|
- **Java 21** auf dem Server. Ältere Java-Versionen starten das Plugin nicht.
|
||||||
- **Simulations Modus (Admin-Test):** Teste alle Türchen vorab durch Datums-Simulation, ohne deine Systemzeit zu ändern. Inklusive einfachem Reset auf das Echtzeit-Datum.
|
- Optional: das Plugin **PlaceholderAPI**, wenn du Werte des Kalenders auf Scoreboards oder Hologrammen anzeigen willst.
|
||||||
- **Präzises Reset-System:** Setze einzelne Tage oder komplette Fortschritte zurück – wahlweise für einzelne Spieler oder global für den ganzen Server.
|
- Optional: eine **MySQL-Datenbank**, wenn mehrere Server (z. B. mit BungeeCord) sich die Daten teilen sollen. Für einen einzelnen Server brauchst du das **nicht**.
|
||||||
- **Immersive Effekte:** Festliche Sound- und Partikeleffekte beim Öffnen eines Türchens.
|
|
||||||
- **Optimierte Tab-Completion:** Intelligente Befehlsvorschläge mit korrekter numerischer Sortierung (1, 2, 3... statt 1, 10, 11...).
|
> Hinweis: Das Plugin wurde für Minecraft 1.20 gebaut. Auf neueren Versionen sollte es laufen, teste es aber vor dem Einsatz auf deinem Live-Server kurz aus.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎄 Adventskalender GUI
|
## 2. Installation in 5 Schritten
|
||||||
|
|
||||||
<div align="center">
|
1. **Server ausschalten** (oder einfach weitermachen, wenn du später neu startest).
|
||||||
|
2. Die Datei **`adventskalender-1.0.3.jar`** in den Ordner **`plugins`** deines Servers kopieren.
|
||||||
|
3. **Server starten.**
|
||||||
|
4. Das Plugin legt automatisch den Ordner **`plugins/Adventskalender`** an. Darin liegen jetzt die Dateien `config.yml`, `messages_de.yml` und `messages_en.yml`.
|
||||||
|
5. **Fertig.** Im Spiel `/ak` eingeben und der Kalender geht auf.
|
||||||
|
|
||||||
<img src="https://git.viper.ipv64.net/M_Viper/Advenskalender/raw/branch/main/img/03.png" width="400" alt="Kalender GUI 1">
|
Wenn du in der Konsole die Meldung **„Adventskalender erfolgreich gestartet!“** siehst, hat alles geklappt.
|
||||||
<img src="https://git.viper.ipv64.net/M_Viper/Advenskalender/raw/branch/main/img/04.png" width="400" alt="Kalender GUI 2">
|
|
||||||
|
|
||||||
*Die intuitive Kalender-Oberfläche mit allen 24 Türchen*
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚙️ Befehle & Permissions
|
## 3. So benutzen Spieler das Plugin
|
||||||
|
|
||||||
### ▶ Spieler-Befehle
|
### Kalender öffnen
|
||||||
|
|
||||||
**`/adventskalender`** (Aliase: `/ak`, `/advent`, `/kalender`)
|
| Befehl | Was passiert |
|
||||||
- **Beschreibung:** Öffnet die Kalender-Oberfläche.
|
|---|---|
|
||||||
- **Berechtigung:** `adventskalender.use`
|
| `/ak` | Öffnet den Kalender, der gerade läuft. Läuft keiner, wird der Adventskalender gezeigt (mit gesperrten Türchen). |
|
||||||
|
| `/ak halloween` | Öffnet den Halloween-Kalender |
|
||||||
|
| `/ak easter` | Öffnet den Oster-Kalender |
|
||||||
|
| `/ak advent` | Öffnet den Adventskalender |
|
||||||
|
|
||||||
### ▶ Admin-Befehle
|
Statt `/ak` funktionieren auch `/adventskalender`, `/advent` und `/kalender`.
|
||||||
|
|
||||||
**`/ak admin test <1-24 | reset>`**
|
### Türchen öffnen
|
||||||
- **Beschreibung:** Simuliert einen bestimmten Tag für Belohnungstests. Nutze `reset`, um zum echten Datum zurückzukehren.
|
|
||||||
- **Berechtigung:** `adventskalender.admin`
|
|
||||||
|
|
||||||
**`/ak admin resetday <global | Spieler> <1-24>`**
|
- Die **Köpfe (Advent) bzw. Items (Halloween, Ostern)** in der Mitte des Menüs sind die Türchen. Die rot-grünen Glasscheiben am Rand sind nur Dekoration.
|
||||||
- **Beschreibung:** Setzt den Status eines spezifischen Tages für einen Spieler oder den gesamten Server zurück.
|
- Ein Türchen kannst du nur öffnen, wenn der **Tag erreicht** ist. Der 5. Dezember schaltet z. B. Türchen 1 bis 5 frei, Türchen 6 bleibt zu.
|
||||||
- **Berechtigung:** `adventskalender.admin`
|
- **Klick auf ein Türchen** = du bekommst das Geschenk. Passt es nicht ins Inventar, fällt es vor deine Füße.
|
||||||
|
- Ein geöffnetes Türchen **glitzert** und kann nicht nochmal geöffnet werden.
|
||||||
|
- Hast du einen Tag **verpasst**, ist das kein Problem: Du kannst alte Türchen jederzeit nachholen, solange der Kalender läuft.
|
||||||
|
|
||||||
**`/ak admin reset <global | Spieler>`**
|
### Geburtstags-Geschenk
|
||||||
- **Beschreibung:** Löscht den kompletten Fortschritt (alle Tage) für einen Spieler oder global.
|
|
||||||
- **Berechtigung:** `adventskalender.admin`
|
|
||||||
|
|
||||||
**`/ak admin reload`**
|
| Befehl | Was passiert |
|
||||||
- **Beschreibung:** Lädt Config, Sprachdateien und Datenbank-Verbindung im laufenden Betrieb neu.
|
|---|---|
|
||||||
- **Berechtigung:** `adventskalender.admin`
|
| `/ak birthday set 15.03` | Trägt deinen Geburtstag ein (Tag.Monat, **ohne Jahr**) |
|
||||||
|
| `/ak birthday` | Zeigt deinen eingetragenen Geburtstag |
|
||||||
|
| `/ak birthday claim` | Holt dein Geschenk ab |
|
||||||
|
|
||||||
|
- Du kannst das Geschenk **am Geburtstag und bis zu 2 Tage danach** abholen (Familie, Freunde, Feier – du hast Zeit).
|
||||||
|
- Es gibt **ein Geschenk pro Jahr**.
|
||||||
|
- Wenn du dich an deinem Geburtstag (oder danach) einloggst, bekommst du eine **anklickbare Nachricht**. Ein Klick darauf holt das Geschenk ab.
|
||||||
|
- Dein Geburtstag kann **nur einmal** eingetragen werden. Hast du dich vertippt, muss ein Admin das ändern (siehe unten).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📊 PlaceholderAPI (PAPI)
|
## 4. Befehle für Admins
|
||||||
|
|
||||||
- **`%ak_c%`** - Anzahl bereits geöffneter Türchen.
|
Alle Admin-Befehle beginnen mit `/ak admin`. Du brauchst dafür das Recht `adventskalender.admin` (Operatoren haben es automatisch).
|
||||||
- **`%ak_n%`** - Nummer des nächsten verfügbaren Türchens.
|
|
||||||
- **`%ak_d%`** - Verbleibende Tage bis zum 25. Dezember.
|
|
||||||
|
|
||||||
### Placeholder auf einem Schild
|
Die Teile in `[eckigen Klammern]` sind **optional**. Lässt du `[event]` weg, gilt der Befehl für **alle** Kalender.
|
||||||
|
Gültige Namen für `[event]`: `advent`, `halloween`, `easter`.
|
||||||
|
|
||||||
<div align="center">
|
| Befehl | Was er macht |
|
||||||
|
|---|---|
|
||||||
|
| `/ak admin reload` | Lädt Config und Sprachdateien neu, ohne Neustart |
|
||||||
|
| `/ak admin test [event] <Tag>` | Tut so, als wäre heute der gewählte Tag (siehe [Testen](#11-alles-testen-ohne-auf-den-dezember-zu-warten)) |
|
||||||
|
| `/ak admin test [event] reset` | Beendet den Test, das echte Datum gilt wieder |
|
||||||
|
| `/ak admin reset [event] <Spieler>` | Löscht **alle** geöffneten Türchen eines Spielers |
|
||||||
|
| `/ak admin reset [event] global` | Löscht **alle** Daten aller Spieler (**Vorsicht!**) |
|
||||||
|
| `/ak admin resetday [event] <Spieler> <Tag>` | Macht **ein** Türchen für einen Spieler wieder zu |
|
||||||
|
| `/ak admin resetday [event] global <Tag>` | Macht ein Türchen im Server-weiten Modus wieder frei |
|
||||||
|
| `/ak admin birthday set <Spieler> <TT.MM>` | Trägt den Geburtstag eines Spielers ein oder ändert ihn |
|
||||||
|
| `/ak admin birthday remove <Spieler>` | Löscht den Geburtstag eines Spielers |
|
||||||
|
| `/ak admin birthday reset <Spieler>` | Erlaubt dem Spieler, das Geburtstagsgeschenk **nochmal** abzuholen (zum Testen oder bei Problemen) |
|
||||||
|
|
||||||
<img src="https://git.viper.ipv64.net/M_Viper/Advenskalender/raw/branch/main/img/02.png" alt="Placeholder Beispiel">
|
**Beispiele:**
|
||||||
|
|
||||||
*Ein Schild, das die Placeholder des Plugins anzeigt.*
|
```
|
||||||
|
/ak admin test halloween 15 (Halloween-Kalender: so tun, als wäre der 15. Tag)
|
||||||
|
/ak admin test reset (alle Tests beenden)
|
||||||
|
/ak admin resetday advent Steve 3 (Steve darf Türchen 3 im Advent nochmal öffnen)
|
||||||
|
/ak admin reset halloween Steve (Steves Halloween-Daten löschen)
|
||||||
|
/ak admin birthday set Steve 24.12 (Steves Geburtstag auf den 24.12. setzen)
|
||||||
|
```
|
||||||
|
|
||||||
</div>
|
> Mit **Tab** vervollständigt Minecraft die Namen und Zahlen für dich.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💬 Support & Community
|
## 5. Rechte (Permissions)
|
||||||
|
|
||||||
Du hast Fragen oder Feedback? Tritt unserem Discord bei!
|
Rechte vergibst du mit einem Rechte-Plugin (z. B. LuckPerms).
|
||||||
|
|
||||||
<div align="center">
|
| Recht | Wofür | Standard |
|
||||||
|
|---|---|---|
|
||||||
[](https://discord.com/invite/FdRs4BRd8D)
|
| `adventskalender.use` | Kalender öffnen und benutzen | **alle Spieler** |
|
||||||
|
| `adventskalender.birthday` | Geburtstag eintragen und Geschenk abholen | **alle Spieler** |
|
||||||
*Klicke auf den Button für direkten Support durch die Community!*
|
| `adventskalender.admin` | Alle Admin-Befehle und Update-Hinweis beim Beitritt | nur Operatoren |
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔧 Technische Details
|
## 6. Die Dateien des Plugins
|
||||||
|
|
||||||
- **Kompatibilität:** Paper, Spigot, Purpur (1.18.x - 1.21.x)
|
Alles liegt in `plugins/Adventskalender/`:
|
||||||
- **Smart Caching:** Minimale Serverlast durch asynchrone Datenbankaufrufe.
|
|
||||||
- **Join-Delay:** Update-Meldungen erscheinen erst nach 2 Sekunden, um den Chat-Spam beim Joinen zu umgehen.
|
| Datei | Wofür |
|
||||||
|
|---|---|
|
||||||
|
| `config.yml` | **Einstellungen und Belohnungen.** Hier änderst du, was es zu gewinnen gibt und wann die Kalender laufen. |
|
||||||
|
| `messages_de.yml` | Alle **deutschen Texte** (Titel, Nachrichten, Namen der Geschenke) |
|
||||||
|
| `messages_en.yml` | Alle **englischen Texte** |
|
||||||
|
| `data.yml` | Hier speichert das Plugin, **wer was geöffnet hat** und die Geburtstage. Diese Datei **nicht löschen**, sonst fangen alle von vorne an. |
|
||||||
|
|
||||||
|
### So bearbeitest du die Dateien richtig
|
||||||
|
|
||||||
|
1. Öffne die Datei mit einem **Texteditor** (Notepad++, VS Code oder der Editor deines Hosters). **Nicht** mit Word.
|
||||||
|
2. Ändere nur die Werte, **nicht** die Einrückung am Zeilenanfang. Die Leerzeichen am Anfang sind wichtig!
|
||||||
|
3. Benutze **Leerzeichen**, keine Tabulatortaste.
|
||||||
|
4. Speichern und im Spiel `/ak admin reload` eingeben.
|
||||||
|
5. Wenn der Server nach einem Fehler die Datei nicht mehr lesen kann: Zeile prüfen, die du zuletzt geändert hast (fehlendes Leerzeichen, fehlende Anführungszeichen).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div align="center">
|
## 7. Belohnungen ändern
|
||||||
|
|
||||||
*Viper Plugins © 2026 - Effizienz und Magie für deinen Server*
|
Jedes Türchen hat in der `config.yml` einen Eintrag. Beispiel Advent, Tag 3:
|
||||||
|
|
||||||
</div>
|
```yaml
|
||||||
|
rewards:
|
||||||
|
3:
|
||||||
|
material: DIAMOND_SWORD
|
||||||
|
amount: 1
|
||||||
|
enchantments:
|
||||||
|
- type: SHARPNESS
|
||||||
|
level: 4
|
||||||
|
- type: UNBREAKING
|
||||||
|
level: 2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Das bedeutet:**
|
||||||
|
|
||||||
|
- `3:` – die Nummer des Türchens
|
||||||
|
- `material:` – welches Item es gibt (siehe unten)
|
||||||
|
- `amount:` – wie viele Stück
|
||||||
|
- `enchantments:` – Verzauberungen. Möchtest du keine, schreibe `enchantments: []`
|
||||||
|
|
||||||
|
### Wo stehen die Belohnungen der anderen Kalender?
|
||||||
|
|
||||||
|
- **Advent:** ganz oben unter `rewards:`
|
||||||
|
- **Halloween:** weiter unten unter `events:` → `halloween:` → `rewards:`
|
||||||
|
- **Ostern:** weiter unten unter `events:` → `easter:` → `rewards:`
|
||||||
|
- **Geburtstag:** ganz unten unter `birthday:` → `rewards:`
|
||||||
|
|
||||||
|
### Item-Namen (`material`)
|
||||||
|
|
||||||
|
Es sind die englischen Minecraft-Namen in **GROSSBUCHSTABEN** mit Unterstrich statt Leerzeichen.
|
||||||
|
Beispiele: `DIAMOND`, `GOLDEN_APPLE`, `NETHERITE_INGOT`, `ELYTRA`, `FIREWORK_ROCKET`.
|
||||||
|
Eine Liste findest du, wenn du im Internet nach „Spigot Material list“ suchst.
|
||||||
|
|
||||||
|
### Verzauberungen (`type`)
|
||||||
|
|
||||||
|
Auch hier die englischen Namen: `SHARPNESS`, `UNBREAKING`, `PROTECTION`, `EFFICIENCY`, `FORTUNE`, `LOOTING`, `MENDING` usw.
|
||||||
|
`level` ist die Stufe (z. B. `5` für Schärfe V).
|
||||||
|
|
||||||
|
### Wenn du etwas falsch schreibst
|
||||||
|
|
||||||
|
Das Plugin gibt dann eine **Warnung in der Konsole** aus (z. B. „Ungültiges Material …“). Der Spieler bekommt die Meldung, dass das Geschenk falsch eingestellt ist, und das **Türchen bleibt zu**. Er verliert also nichts. Korrigiere den Fehler und lade mit `/ak admin reload` neu.
|
||||||
|
|
||||||
|
### Eigener Name und eigene Beschreibung der Geschenke
|
||||||
|
|
||||||
|
Der Name und der Beschreibungstext, die im Menü über einem Türchen stehen, kommen **nicht** aus der `config.yml`, sondern aus der Sprachdatei. Siehe [Texte und Sprache ändern](#10-texte-und-sprache-ändern).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Copyright © 2026 - M_Viper - Alle Rechte vorbehalten**
|
## 8. Kalender ein- und ausschalten oder anpassen
|
||||||
|
|
||||||
Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt.
|
In der `config.yml` ganz unten gibt es den Bereich `events:`. Dort steht jeder Kalender mit seinen Einstellungen:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
events:
|
||||||
|
halloween:
|
||||||
|
enabled: true
|
||||||
|
days: 31
|
||||||
|
start: "10-01"
|
||||||
|
grace_days: 0
|
||||||
|
closed_material: PUMPKIN
|
||||||
|
opened_material: JACK_O_LANTERN
|
||||||
|
```
|
||||||
|
|
||||||
|
| Einstellung | Bedeutung |
|
||||||
|
|---|---|
|
||||||
|
| `enabled` | `true` = Kalender an, `false` = Kalender aus (die Daten bleiben erhalten) |
|
||||||
|
| `days` | Wie viele Türchen der Kalender hat (1 bis 36) |
|
||||||
|
| `start` | Wann der Kalender beginnt. Format `Monat-Tag`, z. B. `"10-01"` für den 1. Oktober |
|
||||||
|
| `grace_days` | Wie viele Tage der Kalender nach dem letzten Türchen noch offen bleibt, damit man verpasste Türchen nachholen kann |
|
||||||
|
| `closed_material` | Item für ein **geschlossenes** Türchen |
|
||||||
|
| `opened_material` | Item für ein **geöffnetes** Türchen |
|
||||||
|
|
||||||
|
### Zeitpunkt für Ostern
|
||||||
|
|
||||||
|
Ostern liegt jedes Jahr woanders. Deshalb gibt es dort eine Sonderschreibweise:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
start: "easter-13"
|
||||||
|
```
|
||||||
|
|
||||||
|
Das heißt: **13 Tage vor Ostersonntag**. Das Plugin rechnet das Datum **jedes Jahr automatisch** aus, du musst nie wieder etwas ändern.
|
||||||
|
Weitere Beispiele: `easter+1` = Ostermontag, `easter-2` = Karfreitag.
|
||||||
|
|
||||||
|
### Eigene Köpfe statt Items (für Fortgeschrittene)
|
||||||
|
|
||||||
|
Möchtest du für Halloween oder Ostern hübsche Kopf-Texturen, kannst du bei einem Kalender zusätzlich diese Zeilen einfügen:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
closed_texture: "http://textures.minecraft.net/texture/DEINE_TEXTUR"
|
||||||
|
opened_texture: "http://textures.minecraft.net/texture/DEINE_TEXTUR"
|
||||||
|
```
|
||||||
|
|
||||||
|
Passende Texturen findest du auf Seiten wie *minecraft-heads.com* (dort den Wert „Minecraft-URL“ kopieren).
|
||||||
|
|
||||||
|
### Zeitzone
|
||||||
|
|
||||||
|
Das Plugin richtet sich nach der Uhrzeit des Servers. Wenn dein Server in einem anderen Land steht und die Türchen zu früh oder zu spät aufgehen, trage deine Zeitzone ein:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
general:
|
||||||
|
timezone: "Europe/Berlin"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Geburtstage einstellen
|
||||||
|
|
||||||
|
Alles steht unten in der `config.yml` im Bereich `birthday:`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
birthday:
|
||||||
|
enabled: true
|
||||||
|
grace_days: 2
|
||||||
|
allow_change: false
|
||||||
|
broadcast: true
|
||||||
|
commands: []
|
||||||
|
rewards:
|
||||||
|
1:
|
||||||
|
material: CAKE
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
```
|
||||||
|
|
||||||
|
| Einstellung | Bedeutung |
|
||||||
|
|---|---|
|
||||||
|
| `enabled` | `false` schaltet die Geburtstags-Funktion komplett ab |
|
||||||
|
| `grace_days` | Wie viele Tage **nach** dem Geburtstag das Geschenk noch abholbar ist (Standard 2) |
|
||||||
|
| `allow_change` | `false` = Spieler können ihren Geburtstag nur **einmal** eintragen, Änderungen macht nur ein Admin. `true` = Spieler dürfen selbst ändern (siehe Hinweis) |
|
||||||
|
| `broadcast` | `true` = der ganze Server sieht „Steve feiert Geburtstag – alles Gute!“, wenn das Geschenk abgeholt wird |
|
||||||
|
| `commands` | Zusätzliche Befehle, die der Server ausführt (siehe unten) |
|
||||||
|
| `rewards` | Die Geschenke. Funktioniert genau wie bei den Kalendern, du kannst beliebig viele Einträge anlegen (1, 2, 3, …) |
|
||||||
|
|
||||||
|
> **Warum ist `allow_change` standardmäßig aus?**
|
||||||
|
> Wäre es an, könnte ein Spieler sein Datum jeden Tag umstellen und sich ständig Geschenke holen. Auch mit `true` kann er pro Jahr nur **einmal** abholen.
|
||||||
|
|
||||||
|
### Geld oder andere Belohnungen per Befehl
|
||||||
|
|
||||||
|
Wenn du z. B. ein Wirtschafts-Plugin hast, kannst du Geld verschenken:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
commands:
|
||||||
|
- "eco give %player% 500"
|
||||||
|
- "give %player% minecraft:cake 1"
|
||||||
|
```
|
||||||
|
|
||||||
|
`%player%` wird automatisch durch den Namen des Spielers ersetzt. Ohne Schrägstrich am Anfang schreiben.
|
||||||
|
|
||||||
|
### Wichtige Hinweise
|
||||||
|
|
||||||
|
- Das **Jahr** wird nie gespeichert, nur Tag und Monat. Das Plugin weiß also nie, wie alt jemand ist.
|
||||||
|
- Wer am **29. Februar** Geburtstag hat, feiert in Jahren ohne Schalttag am **28. Februar**.
|
||||||
|
- Wer am **31. Dezember** Geburtstag hat, kann bis in den Januar abholen. Das funktioniert korrekt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Texte und Sprache ändern
|
||||||
|
|
||||||
|
### Sprache umstellen
|
||||||
|
|
||||||
|
In der `config.yml` ganz oben:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
general:
|
||||||
|
language: "de"
|
||||||
|
```
|
||||||
|
|
||||||
|
`"de"` = Deutsch, `"en"` = Englisch. Danach `/ak admin reload`.
|
||||||
|
|
||||||
|
### Eigene Texte
|
||||||
|
|
||||||
|
Öffne `messages_de.yml` (oder `messages_en.yml`). Dort stehen alle Texte. Farben schreibst du mit `&`:
|
||||||
|
|
||||||
|
| Code | Farbe | Code | Farbe |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `&0` | Schwarz | `&8` | Dunkelgrau |
|
||||||
|
| `&1` | Dunkelblau | `&9` | Blau |
|
||||||
|
| `&2` | Dunkelgrün | `&a` | Grün |
|
||||||
|
| `&3` | Türkis | `&b` | Hellblau |
|
||||||
|
| `&4` | Dunkelrot | `&c` | Rot |
|
||||||
|
| `&5` | Lila | `&d` | Pink |
|
||||||
|
| `&6` | Gold | `&e` | Gelb |
|
||||||
|
| `&7` | Grau | `&f` | Weiß |
|
||||||
|
|
||||||
|
Formatierung: `&l` = **fett**, `&o` = *kursiv*, `&n` = unterstrichen, `&r` = zurücksetzen.
|
||||||
|
|
||||||
|
### Platzhalter in Texten
|
||||||
|
|
||||||
|
Diese Platzhalter dürfen in den Texten stehen und werden automatisch ersetzt:
|
||||||
|
|
||||||
|
- `%day%` – die Nummer des Tages
|
||||||
|
- `%date%` – das Datum eines gesperrten Türchens
|
||||||
|
- `%player%` – der Spielername
|
||||||
|
- `%event%` – der Name des Kalenders
|
||||||
|
- `%days%` – die Anzahl Tage (bei Geburtstagen)
|
||||||
|
|
||||||
|
### Namen und Beschreibung der Geschenke im Menü
|
||||||
|
|
||||||
|
Beispiel für den Adventskalender in `messages_de.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
rewards:
|
||||||
|
day_3:
|
||||||
|
name: "&cTag 3: Schwert des Helden"
|
||||||
|
lore: ["&7Ein mächtiges Schwert, um Monster zu jagen."]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `name` ist die Überschrift des Türchens.
|
||||||
|
- `lore` sind die Zeilen darunter. Mehrere Zeilen schreibst du so: `["Zeile 1", "Zeile 2"]`.
|
||||||
|
|
||||||
|
Für Halloween und Ostern stehen die Texte weiter unten in der Datei unter `events:` → `halloween:` bzw. `easter:`.
|
||||||
|
Wenn du ein Geschenk in der `config.yml` änderst, solltest du hier den Namen anpassen, sonst steht „Schwert des Helden“, obwohl es jetzt Diamanten gibt.
|
||||||
|
|
||||||
|
> Wenn du neue Texte vermisst („Key error: …“ im Spiel), siehe [Plugin aktualisieren](#15-plugin-aktualisieren).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Alles testen, ohne auf den Dezember zu warten
|
||||||
|
|
||||||
|
Du musst nicht bis zum 1. Dezember warten, um alles auszuprobieren:
|
||||||
|
|
||||||
|
1. `/ak admin test advent 10` – der Adventskalender denkt jetzt, es sei der 10. Dezember.
|
||||||
|
2. `/ak advent` – öffne ihn und probiere die Türchen 1 bis 10 aus.
|
||||||
|
3. Zum Zurücksetzen deiner Test-Türchen: `/ak admin reset advent DEINNAME`
|
||||||
|
4. Test beenden: `/ak admin test reset`
|
||||||
|
|
||||||
|
Genauso für `halloween` und `easter`. Den Test-Tag **vergisst** das Plugin beim nächsten Neustart. Du kannst also nichts „vergessen auszuschalten“.
|
||||||
|
|
||||||
|
**Geburtstag testen:**
|
||||||
|
|
||||||
|
1. `/ak admin birthday set DEINNAME TT.MM` – statt TT.MM das **heutige** Datum eintragen (z. B. `24.12`)
|
||||||
|
2. `/ak birthday claim` – du bekommst das Geschenk
|
||||||
|
3. Nochmal testen: `/ak admin birthday reset DEINNAME`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Jeder darf nur einmal – oder der ganze Server nur einmal?
|
||||||
|
|
||||||
|
Es gibt zwei Betriebsarten:
|
||||||
|
|
||||||
|
- **Normal (Standard):** Jeder Spieler hat seinen **eigenen** Kalender. Jeder darf jedes Türchen einmal öffnen.
|
||||||
|
- **Global:** Jedes Türchen darf **nur ein einziger Spieler** auf dem ganzen Server öffnen. Wer zuerst klickt, gewinnt.
|
||||||
|
|
||||||
|
Umstellen in der `config.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
calendar:
|
||||||
|
use_global_calendar: false # true = Global-Modus
|
||||||
|
```
|
||||||
|
|
||||||
|
Du kannst das auch nur für **einen** Kalender ändern, indem du bei diesem im Bereich `events:` die Zeile `global: true` oder `global: false` einfügst.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Mehrere Server mit einer Datenbank (MySQL)
|
||||||
|
|
||||||
|
Nur nötig, wenn du z. B. ein BungeeCord-Netzwerk hast und alle Server dieselben Daten benutzen sollen. Für **einen** Server einfach ignorieren.
|
||||||
|
|
||||||
|
In der `config.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
mysql:
|
||||||
|
enable: true
|
||||||
|
host: "localhost"
|
||||||
|
port: 3306
|
||||||
|
database: "adventskalender"
|
||||||
|
user: "root"
|
||||||
|
password: "DEIN_PASSWORT"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Die Datenbank (`adventskalender`) musst du vorher selbst anlegen. Die Tabellen darin erstellt das Plugin allein.
|
||||||
|
- Danach den Server **neu starten** (oder `/ak admin reload`).
|
||||||
|
- In der Konsole muss stehen: **„MySQL-Verbindung erfolgreich hergestellt!“**
|
||||||
|
- Klappt es nicht, benutzt das Plugin automatisch die `data.yml` als Notlösung und schreibt eine Warnung in die Konsole.
|
||||||
|
- Sollte die Datenbank später kurz ausfallen, bekommen Spieler eine Meldung und es wird **nichts** vergeben. Sobald die Datenbank wieder da ist, geht alles weiter.
|
||||||
|
- Alle Kalender und Geburtstage werden in der Datenbank gespeichert. Daten aus einer älteren Version werden dabei automatisch übernommen.
|
||||||
|
|
||||||
|
> **Wichtig:** Daten aus der `data.yml` werden **nicht automatisch** in die Datenbank kopiert. Schaltest du MySQL ein, fangen die Spieler dort bei null an.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Platzhalter für Scoreboards, Hologramme & Co.
|
||||||
|
|
||||||
|
Wenn das Plugin **PlaceholderAPI** installiert ist, kannst du diese Werte überall anzeigen, wo PlaceholderAPI funktioniert:
|
||||||
|
|
||||||
|
### Für das gerade laufende Kalender-Event
|
||||||
|
|
||||||
|
| Platzhalter | Zeigt |
|
||||||
|
|---|---|
|
||||||
|
| `%ak_event%` | Name des laufenden Kalenders (`advent`, `halloween`, `easter`) |
|
||||||
|
| `%ak_day%` | Welcher Tag des Kalenders heute ist (0 = Kalender läuft nicht) |
|
||||||
|
| `%ak_claimed%` | Wie viele Türchen der Spieler schon geöffnet hat |
|
||||||
|
| `%ak_remaining%` | Wie viele Türchen noch offen sind |
|
||||||
|
| `%ak_has_claimed_today%` | „Ja“ oder „Nein“: Hat der Spieler das heutige Türchen schon geöffnet? |
|
||||||
|
| `%ak_is_global%` | „Global“ oder „Spieler-basiert“ |
|
||||||
|
|
||||||
|
### Für einen bestimmten Kalender
|
||||||
|
|
||||||
|
Schreibe den Namen des Kalenders vor den Platzhalter:
|
||||||
|
|
||||||
|
- `%ak_halloween_claimed%`
|
||||||
|
- `%ak_easter_day%`
|
||||||
|
- `%ak_advent_remaining%`
|
||||||
|
|
||||||
|
### Geburtstag
|
||||||
|
|
||||||
|
| Platzhalter | Zeigt |
|
||||||
|
|---|---|
|
||||||
|
| `%ak_birthday%` | Eingetragenes Datum (z. B. `15.03.`) oder `-` |
|
||||||
|
| `%ak_birthday_active%` | „Ja“, wenn der Spieler gerade sein Geschenk abholen kann, sonst „Nein“ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Plugin aktualisieren
|
||||||
|
|
||||||
|
1. Server **ausschalten**.
|
||||||
|
2. Die **alte `.jar`-Datei** aus dem `plugins`-Ordner löschen.
|
||||||
|
3. Die **neue `.jar`-Datei** hineinkopieren.
|
||||||
|
4. Server starten.
|
||||||
|
|
||||||
|
Deine **Daten** (`data.yml`) und deine **Einstellungen** bleiben erhalten.
|
||||||
|
|
||||||
|
### Neue Einstellungen sichtbar machen
|
||||||
|
|
||||||
|
Deine alte `config.yml` und die Sprachdateien werden bei einem Update **nicht überschrieben**. Neue Funktionen laufen trotzdem mit den Standardwerten, du siehst die neuen Einstellungen aber nicht in der Datei. Willst du sie sehen und ändern:
|
||||||
|
|
||||||
|
1. Server ausschalten.
|
||||||
|
2. `config.yml` umbenennen, z. B. in `config_alt.yml`. (Dasselbe mit den `messages_...yml`-Dateien, falls nötig.)
|
||||||
|
3. Server starten. Das Plugin erstellt eine frische, vollständige Datei.
|
||||||
|
4. Deine Änderungen aus `config_alt.yml` in die neue Datei übertragen.
|
||||||
|
|
||||||
|
Die `data.yml` bleibt dabei **immer unangetastet**.
|
||||||
|
|
||||||
|
### Update-Hinweis
|
||||||
|
|
||||||
|
Erscheint eine neue Version auf SpigotMC, schreibt das Plugin eine Warnung in die Konsole. Admins bekommen beim Einloggen eine klickbare Nachricht mit dem Download-Link.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. Probleme und ihre Lösung
|
||||||
|
|
||||||
|
| Problem | Lösung |
|
||||||
|
|---|---|
|
||||||
|
| **Plugin startet nicht** | Prüfe die Java-Version (**Java 21** nötig) und ob es Spigot/Paper ab 1.20 ist. Die genaue Fehlermeldung steht in der Server-Konsole. |
|
||||||
|
| **`/ak` tut nichts / „Unbekannter Befehl“** | Plugin wurde nicht geladen. Schau in die Konsole oder tippe `/plugins` und prüfe, ob „Adventskalender“ grün ist. |
|
||||||
|
| **„Dafür hast du keine Berechtigung“** | Der Spieler hat `adventskalender.use` (bzw. `adventskalender.admin`) nicht. Rechte prüfen. |
|
||||||
|
| **Alle Türchen sind gesperrt** | Es ist gerade nicht die Zeit des Kalenders. Prüfe das Datum oder teste mit `/ak admin test`. |
|
||||||
|
| **Im Menü steht „Key error: …“** | In deiner Sprachdatei fehlt ein Text. Siehe [Plugin aktualisieren](#15-plugin-aktualisieren), neue Sprachdatei erzeugen lassen. |
|
||||||
|
| **Ein Türchen lässt sich nicht öffnen, Meldung „falsch konfiguriert“** | In der `config.yml` ist bei diesem Tag ein Item- oder Verzauberungsname falsch geschrieben. Die Konsole sagt dir genau, wo. |
|
||||||
|
| **Eine Verzauberung fehlt am Geschenk** | Der Name in `type:` ist falsch (englisch und GROSS schreiben). Die Konsole zeigt eine Warnung. |
|
||||||
|
| **Kopf zeigt Steve statt Weihnachtsmotiv** | Der Server konnte die Textur nicht laden (Internet-/Mojang-Problem). Neustart versuchen. Bei Halloween/Ostern gibt es Items statt Köpfen, das ist normal. |
|
||||||
|
| **Meldung „Die Datenbank ist gerade nicht erreichbar“** | Nur mit MySQL. Prüfe Zugangsdaten und ob die Datenbank läuft. |
|
||||||
|
| **Nach dem Reload stimmt etwas nicht** | Server einmal komplett neu starten. |
|
||||||
|
| **Ein Spieler hat sich beim Geburtstag vertippt** | `/ak admin birthday set <Spieler> <TT.MM>` |
|
||||||
|
| **Spieler hat sein Geburtstagsgeschenk verloren / Fehler beim Abholen** | `/ak admin birthday reset <Spieler>`, dann darf er nochmal abholen. |
|
||||||
|
| **Kalender geht zur falschen Uhrzeit auf** | Zeitzone in der `config.yml` einstellen (siehe [Zeitzone](#zeitzone)). |
|
||||||
|
|
||||||
|
Hilft nichts davon: Kopiere die **Fehlermeldung aus der Konsole** (nicht nur „geht nicht“) und frage auf der Seite des Plugins bei SpigotMC nach.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. Häufige Fragen
|
||||||
|
|
||||||
|
**Kann ich Geschenke für jeden Tag selbst festlegen?**
|
||||||
|
Ja, siehe [Belohnungen ändern](#7-belohnungen-ändern).
|
||||||
|
|
||||||
|
**Kann ich einen Kalender ausschalten, den ich nicht will?**
|
||||||
|
Ja: In der `config.yml` bei `events:` den Eintrag `enabled: false` setzen.
|
||||||
|
|
||||||
|
**Kann ich mehr als 24, 31 oder 14 Türchen haben?**
|
||||||
|
Ja, bis zu 36 pro Kalender (Einstellung `days`). Du musst dann aber auch für jedes neue Türchen eine Belohnung eintragen und im Sprachfile einen Namen vergeben.
|
||||||
|
|
||||||
|
**Was passiert, wenn der Kalender vorbei ist?**
|
||||||
|
Er bleibt noch `grace_days` Tage offen (Advent: bis zum 31.12., Ostern: 7 Tage), danach sind alle Türchen gesperrt, bis er im nächsten Jahr wieder startet.
|
||||||
|
|
||||||
|
**Gehen die Türchen jedes Jahr von vorne los?**
|
||||||
|
Nein. Das Plugin merkt sich dauerhaft, was jemand geöffnet hat. Soll im nächsten Jahr wieder alles neu sein, benutze `/ak admin reset global` (oder `/ak admin reset advent global` nur für den Advent). **Achtung:** Das löscht die Daten für **alle**.
|
||||||
|
|
||||||
|
**Sehen alle Spieler, wann ich Geburtstag habe?**
|
||||||
|
Nein. Das Datum sieht nur der Spieler selbst (und Admins in der Datenbank/Datei). Im Chat steht beim Abholen nur „Spieler feiert Geburtstag“, ohne Datum.
|
||||||
|
|
||||||
|
**Funktioniert das Plugin mit Bedrock-Spielern (Geyser)?**
|
||||||
|
Das wurde nicht getestet.
|
||||||
|
|
||||||
|
**Ich habe `data.yml` gelöscht, was jetzt?**
|
||||||
|
Dann haben alle Spieler wieder alle Türchen und Geburtstage sind weg. Spiele vorher eine Sicherung ein, wenn du eine hast. Mach vor Änderungen am Server grundsätzlich eine Sicherheitskopie des Plugin-Ordners.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Autor: M_Viper • Frohe Weihnachten, schaurig-schönes Halloween, frohe Ostern und alles Gute zum Geburtstag! 🎄🎃🐰🎂*
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>de.mviper</groupId>
|
<groupId>de.mviper</groupId>
|
||||||
<artifactId>adventskalender</artifactId>
|
<artifactId>adventskalender</artifactId>
|
||||||
<version>1.0.2</version>
|
<version>1.0.3</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
@@ -4,11 +4,26 @@ import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.util.HashSet;
|
||||||
import java.time.Month;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platzhalter: %ak_claimed%, %ak_day%, %ak_has_claimed_today%, %ak_remaining%, %ak_is_global%, %ak_event%
|
||||||
|
* beziehen sich auf das laufende Event (sonst das erste aktivierte). Mit Event-Präfix, z.B.
|
||||||
|
* %ak_halloween_claimed%, gilt der Platzhalter für genau dieses Event.
|
||||||
|
*/
|
||||||
public class AdventCalendarExpansion extends PlaceholderExpansion {
|
public class AdventCalendarExpansion extends PlaceholderExpansion {
|
||||||
|
|
||||||
|
private static final long CACHE_MILLIS = 5000L;
|
||||||
|
|
||||||
|
private record Cached(Set<Integer> days, long time) {}
|
||||||
|
|
||||||
|
// Kurzer Cache, damit Scoreboards/Hologramme die Datenbank nicht jede Sekunde abfragen
|
||||||
|
private final ConcurrentHashMap<String, Cached> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull String getIdentifier() {
|
public @NotNull String getIdentifier() {
|
||||||
return "ak";
|
return "ak";
|
||||||
@@ -21,7 +36,7 @@ public class AdventCalendarExpansion extends PlaceholderExpansion {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull String getVersion() {
|
public @NotNull String getVersion() {
|
||||||
return "2.0.0";
|
return Adventskalender.getInstance().getDescription().getVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -29,39 +44,93 @@ public class AdventCalendarExpansion extends PlaceholderExpansion {
|
|||||||
return true; // Expansion bleibt nach PAPI-Reload registriert
|
return true; // Expansion bleibt nach PAPI-Reload registriert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<Integer> claimedDays(Player player, CalendarEvent ev) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
String key = player.getUniqueId() + ":" + ev.getId();
|
||||||
|
Cached c = cache.get(key);
|
||||||
|
if (c != null && now - c.time() < CACHE_MILLIS) return c.days();
|
||||||
|
Set<Integer> days = DataHandler.getViewClaimedDays(player, ev);
|
||||||
|
if (days == null) days = new HashSet<>();
|
||||||
|
cache.put(key, new Cached(days, now));
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record CachedBirthday(Optional<DataHandler.Birthday> birthday, long time) {}
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<UUID, CachedBirthday> birthdayCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private String birthdayPlaceholder(Player player, boolean activeOnly) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
CachedBirthday c = birthdayCache.get(player.getUniqueId());
|
||||||
|
if (c == null || now - c.time() >= CACHE_MILLIS * 6) {
|
||||||
|
try {
|
||||||
|
c = new CachedBirthday(DataHandler.getBirthday(player.getUniqueId()), now);
|
||||||
|
} catch (DataHandler.DataException e) {
|
||||||
|
return activeOnly ? "Nein" : "-";
|
||||||
|
}
|
||||||
|
birthdayCache.put(player.getUniqueId(), c);
|
||||||
|
}
|
||||||
|
if (c.birthday().isEmpty()) return activeOnly ? "Nein" : "-";
|
||||||
|
DataHandler.Birthday b = c.birthday().get();
|
||||||
|
if (!activeOnly) return BirthdayManager.format(b.month(), b.day());
|
||||||
|
Integer year = BirthdayManager.activeYear(b.month(), b.day(),
|
||||||
|
java.time.LocalDate.now(Adventskalender.getInstance().getZone()));
|
||||||
|
return (year != null && b.claimedYear() < year) ? "Ja" : "Nein";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String onPlaceholderRequest(Player player, String identifier) {
|
public String onPlaceholderRequest(Player player, String identifier) {
|
||||||
if (player == null) return "";
|
if (player == null) return "";
|
||||||
|
|
||||||
// Aktuellen Tag ermitteln (Berücksichtigt den test_day aus der Config)
|
String id = identifier.toLowerCase();
|
||||||
int testDay = Adventskalender.getInstance().getConfig().getInt("general.test_day", 0);
|
|
||||||
LocalDate date = LocalDate.now();
|
|
||||||
int currentDay = (testDay > 0) ? testDay : (date.getMonth() == Month.DECEMBER ? date.getDayOfMonth() : 0);
|
|
||||||
|
|
||||||
switch (identifier.toLowerCase()) {
|
// %ak_birthday% - eingetragener Geburtstag (TT.MM.) oder "-"
|
||||||
|
// %ak_birthday_active% - "Ja", wenn das Geburtstagsgeschenk gerade abholbar ist
|
||||||
|
if (id.equals("birthday") || id.equals("birthday_active")) {
|
||||||
|
return birthdayPlaceholder(player, id.equals("birthday_active"));
|
||||||
|
}
|
||||||
|
|
||||||
|
CalendarEvent ev = null;
|
||||||
|
|
||||||
|
// Optionales Event-Präfix, z.B. "halloween_claimed"
|
||||||
|
for (CalendarEvent candidate : EventManager.all()) {
|
||||||
|
if (id.startsWith(candidate.getId() + "_")) {
|
||||||
|
ev = candidate;
|
||||||
|
id = id.substring(candidate.getId().length() + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev == null) ev = EventManager.defaultEvent();
|
||||||
|
if (ev == null) return "";
|
||||||
|
|
||||||
|
int currentDay = ev.currentDay();
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
|
||||||
|
// %ak_event% - ID des Events
|
||||||
|
case "event":
|
||||||
|
return ev.getId();
|
||||||
|
|
||||||
// %ak_claimed% - Anzahl der bereits geöffneten Türchen
|
// %ak_claimed% - Anzahl der bereits geöffneten Türchen
|
||||||
case "claimed":
|
case "claimed":
|
||||||
return String.valueOf(DataHandler.getClaimedDays(player).size());
|
return String.valueOf(claimedDays(player, ev).size());
|
||||||
|
|
||||||
// %ak_day% - Der aktuelle Tag des Adventskalenders (1-24)
|
// %ak_day% - Der aktuelle Tag des Kalenders
|
||||||
case "day":
|
case "day":
|
||||||
return String.valueOf(currentDay);
|
return String.valueOf(currentDay);
|
||||||
|
|
||||||
// %ak_has_claimed_today% - Gibt "Ja" oder "Nein" zurück, ob heute schon geöffnet wurde
|
// %ak_has_claimed_today% - Gibt "Ja" oder "Nein" zurück, ob heute schon geöffnet wurde
|
||||||
case "has_claimed_today":
|
case "has_claimed_today":
|
||||||
if (currentDay == 0) return "Keine Adventszeit";
|
if (currentDay == 0) return "Keine Adventszeit";
|
||||||
return DataHandler.hasClaimed(player, currentDay) ? "Ja" : "Nein";
|
return claimedDays(player, ev).contains(currentDay) ? "Ja" : "Nein";
|
||||||
|
|
||||||
// %ak_remaining% - Wie viele Türchen sind noch offen (für diesen Spieler)?
|
// %ak_remaining% - Wie viele Türchen sind noch offen?
|
||||||
case "remaining":
|
case "remaining":
|
||||||
int claimedCount = DataHandler.getClaimedDays(player).size();
|
return String.valueOf(ev.getDays() - claimedDays(player, ev).size());
|
||||||
return String.valueOf(24 - claimedCount);
|
|
||||||
|
|
||||||
// %ak_is_global% - Zeigt an, ob der globale Modus aktiv ist
|
// %ak_is_global% - Zeigt an, ob der globale Modus aktiv ist
|
||||||
case "is_global":
|
case "is_global":
|
||||||
boolean isGlobal = Adventskalender.getInstance().getConfig().getBoolean("calendar.use_global_calendar", false);
|
return ev.isGlobal() ? "Global" : "Spieler-basiert";
|
||||||
return isGlobal ? "Global" : "Spieler-basiert";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // Placeholder unbekannt
|
return null; // Placeholder unbekannt
|
||||||
|
|||||||
@@ -2,14 +2,20 @@ package de.mviper.adventskalender;
|
|||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.*;
|
import org.bukkit.command.*;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import java.time.MonthDay;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class AdventCommand implements CommandExecutor, TabCompleter {
|
public class AdventCommand implements CommandExecutor, TabCompleter {
|
||||||
|
|
||||||
|
private static final int MAX_DAYS = 36;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||||
|
Adventskalender plugin = Adventskalender.getInstance();
|
||||||
|
|
||||||
if (args.length > 0 && args[0].equalsIgnoreCase("admin")) {
|
if (args.length > 0 && args[0].equalsIgnoreCase("admin")) {
|
||||||
if (!sender.hasPermission("adventskalender.admin")) {
|
if (!sender.hasPermission("adventskalender.admin")) {
|
||||||
sender.sendMessage(LanguageManager.getMessage("messages.no_permission"));
|
sender.sendMessage(LanguageManager.getMessage("messages.no_permission"));
|
||||||
@@ -18,143 +24,353 @@ public class AdventCommand implements CommandExecutor, TabCompleter {
|
|||||||
|
|
||||||
// /ak admin reload
|
// /ak admin reload
|
||||||
if (args.length == 2 && args[1].equalsIgnoreCase("reload")) {
|
if (args.length == 2 && args[1].equalsIgnoreCase("reload")) {
|
||||||
Adventskalender.getInstance().reloadConfig();
|
plugin.reloadConfig();
|
||||||
|
plugin.reloadSettings();
|
||||||
LanguageManager.setup();
|
LanguageManager.setup();
|
||||||
DataHandler.setup();
|
DataHandler.setup();
|
||||||
|
AdventInventory.initCache();
|
||||||
sender.sendMessage(LanguageManager.getMessage("messages.admin.reload_success"));
|
sender.sendMessage(LanguageManager.getMessage("messages.admin.reload_success"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /ak admin test <Tag/reset>
|
// /ak admin test [Event] <Tag/reset>
|
||||||
if (args.length == 3 && args[1].equalsIgnoreCase("test")) {
|
if ((args.length == 3 || args.length == 4) && args[1].equalsIgnoreCase("test")) {
|
||||||
if (args[2].equalsIgnoreCase("reset")) {
|
CalendarEvent ev = null;
|
||||||
Adventskalender.getInstance().getConfig().set("general.test_day", 0);
|
if (args.length == 4) {
|
||||||
Adventskalender.getInstance().saveConfig();
|
ev = EventManager.getAny(args[2]);
|
||||||
sender.sendMessage("§e[Admin] §7Test-Modus §cbeendet§7. Das echte Datum wird nun verwendet.");
|
if (ev == null) {
|
||||||
|
sendUnknownEvent(sender, args[2]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String value = args[args.length - 1];
|
||||||
|
List<CalendarEvent> targets = ev != null ? List.of(ev) : EventManager.allConfigured();
|
||||||
|
|
||||||
|
if (value.equalsIgnoreCase("reset")) {
|
||||||
|
for (CalendarEvent t : targets) t.setTestDay(0);
|
||||||
|
sender.sendMessage(LanguageManager.getString("messages.admin.test_mode_off").replace("%event%", label(ev)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
int day = parseDay(value, ev != null ? ev.getDays() : MAX_DAYS);
|
||||||
int day = Integer.parseInt(args[2]);
|
if (day == -1) {
|
||||||
if (day < 1 || day > 24) {
|
sender.sendMessage(LanguageManager.getString("messages.admin.invalid_day"));
|
||||||
sender.sendMessage("§cBitte gib einen Tag zwischen 1 und 24 an (oder 'reset').");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Adventskalender.getInstance().getConfig().set("general.test_day", day);
|
for (CalendarEvent t : targets) t.setTestDay(day);
|
||||||
Adventskalender.getInstance().saveConfig();
|
sender.sendMessage(LanguageManager.getString("messages.admin.test_mode_on")
|
||||||
sender.sendMessage("§e[Admin] §7Test-Tag auf §6" + day + " §7gesetzt.");
|
.replace("%day%", String.valueOf(day)).replace("%event%", label(ev)));
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
sender.sendMessage("§cBitte gib eine gültige Zahl (1-24) oder 'reset' an.");
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /ak admin resetday <global/Spieler> <Tag>
|
// /ak admin resetday [Event] <global/Spieler> <Tag>
|
||||||
if (args.length == 4 && args[1].equalsIgnoreCase("resetday")) {
|
if ((args.length == 4 || args.length == 5) && args[1].equalsIgnoreCase("resetday")) {
|
||||||
String target = args[2];
|
CalendarEvent ev = null;
|
||||||
try {
|
int shift = 0;
|
||||||
int day = Integer.parseInt(args[3]);
|
if (args.length == 5) {
|
||||||
if (day < 1 || day > 24) throw new NumberFormatException();
|
ev = EventManager.getAny(args[2]);
|
||||||
|
if (ev == null) {
|
||||||
|
sendUnknownEvent(sender, args[2]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
shift = 1;
|
||||||
|
}
|
||||||
|
String target = args[2 + shift];
|
||||||
|
int day = parseDay(args[3 + shift], ev != null ? ev.getDays() : MAX_DAYS);
|
||||||
|
if (day == -1) {
|
||||||
|
sender.sendMessage(LanguageManager.getString("messages.admin.invalid_day"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CalendarEvent event = ev;
|
||||||
|
runAsync(() -> {
|
||||||
if (target.equalsIgnoreCase("global")) {
|
if (target.equalsIgnoreCase("global")) {
|
||||||
DataHandler.GlobalDataManager.resetGlobalDay(day);
|
DataHandler.resetGlobalDay(event, day);
|
||||||
sender.sendMessage("§a[Admin] §7Tag §6" + day + " §7global zurückgesetzt.");
|
reply(sender, LanguageManager.getString("messages.admin.reset_day_global")
|
||||||
|
.replace("%day%", String.valueOf(day)).replace("%event%", label(event)));
|
||||||
} else {
|
} else {
|
||||||
DataHandler.resetPlayerDay(target, day);
|
boolean found = DataHandler.resetPlayerDay(target, event, day);
|
||||||
sender.sendMessage("§a[Admin] §7Tag §6" + day + " §7für §e" + target + " §7zurückgesetzt.");
|
reply(sender, found
|
||||||
}
|
? LanguageManager.getString("messages.admin.reset_day_player")
|
||||||
} catch (NumberFormatException e) {
|
.replace("%day%", String.valueOf(day)).replace("%player%", target).replace("%event%", label(event))
|
||||||
sender.sendMessage("§cTag muss zwischen 1 und 24 liegen.");
|
: LanguageManager.getString("messages.admin.player_not_found").replace("%player%", target));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /ak admin reset <global/Spieler>
|
// /ak admin reset [Event] <global/Spieler>
|
||||||
if (args.length >= 3 && args[1].equalsIgnoreCase("reset")) {
|
if (args.length >= 3 && args[1].equalsIgnoreCase("reset")) {
|
||||||
String baseTitle = LanguageManager.getString("gui.title");
|
CalendarEvent ev = null;
|
||||||
if (args[2].equalsIgnoreCase("global")) {
|
|
||||||
DataHandler.resetAll();
|
|
||||||
for (Player online : Bukkit.getOnlinePlayers()) {
|
|
||||||
if (online.getOpenInventory().getTitle().contains(baseTitle)) online.closeInventory();
|
|
||||||
}
|
|
||||||
sender.sendMessage("§a[Admin] §7Kompletter Reset durchgeführt.");
|
|
||||||
} else {
|
|
||||||
String targetName = args[2];
|
String targetName = args[2];
|
||||||
Player target = Bukkit.getPlayer(targetName);
|
if (args.length >= 4) {
|
||||||
if (target != null) {
|
ev = EventManager.getAny(args[2]);
|
||||||
DataHandler.resetPlayer(target);
|
if (ev == null) {
|
||||||
if (target.getOpenInventory().getTitle().contains(baseTitle)) target.closeInventory();
|
sendUnknownEvent(sender, args[2]);
|
||||||
sender.sendMessage("§a[Admin] §7Daten für §e" + target.getName() + " §7gelöscht.");
|
return true;
|
||||||
|
}
|
||||||
|
targetName = args[3];
|
||||||
|
}
|
||||||
|
final CalendarEvent event = ev;
|
||||||
|
final String name = targetName;
|
||||||
|
|
||||||
|
if (name.equalsIgnoreCase("global")) {
|
||||||
|
runAsync(() -> {
|
||||||
|
DataHandler.resetAll(event);
|
||||||
|
Bukkit.getScheduler().runTask(plugin, AdventInventory::closeAll);
|
||||||
|
reply(sender, LanguageManager.getString("messages.admin.reset_all").replace("%event%", label(event)));
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
DataHandler.resetOfflinePlayer(targetName);
|
Player target = Bukkit.getPlayerExact(name);
|
||||||
sender.sendMessage("§a[Admin] §7Offline-Daten für §e" + targetName + " §7gelöscht.");
|
if (target != null) {
|
||||||
|
runAsync(() -> {
|
||||||
|
DataHandler.resetPlayer(target, event);
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> AdventInventory.closeFor(target));
|
||||||
|
reply(sender, LanguageManager.getString("messages.admin.reset_player")
|
||||||
|
.replace("%player%", target.getName()).replace("%event%", label(event)));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
runAsync(() -> {
|
||||||
|
boolean found = DataHandler.resetOfflinePlayer(name, event);
|
||||||
|
reply(sender, found
|
||||||
|
? LanguageManager.getString("messages.admin.reset_offline")
|
||||||
|
.replace("%player%", name).replace("%event%", label(event))
|
||||||
|
: LanguageManager.getString("messages.admin.player_not_found").replace("%player%", name));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sender.sendMessage("§8§m---§r §6Advent Admin §8§m---");
|
// /ak admin birthday <set|remove|reset> <Spieler> [TT.MM]
|
||||||
sender.sendMessage("§e/ak admin reload §7- Config neu laden");
|
if (args.length >= 4 && args[1].equalsIgnoreCase("birthday")) {
|
||||||
sender.sendMessage("§e/ak admin test <1-24/reset> §7- Datum simulieren");
|
String action = args[2].toLowerCase();
|
||||||
sender.sendMessage("§e/ak admin resetday <global/Spieler> <Tag> §7- Tag löschen");
|
String targetName = args[3];
|
||||||
sender.sendMessage("§e/ak admin reset <global/Spieler> §7- Alles löschen");
|
MonthDay date = null;
|
||||||
|
if (action.equals("set")) {
|
||||||
|
date = args.length == 5 ? BirthdayManager.parse(args[4]) : null;
|
||||||
|
if (date == null) {
|
||||||
|
sender.sendMessage(LanguageManager.getMessage("birthday.invalid_date", "birthday.prefix"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (!action.equals("remove") && !action.equals("reset")) {
|
||||||
|
for (String line : LanguageManager.getStringList("messages.admin.help")) sender.sendMessage(line);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(sender instanceof Player player)) return true;
|
final MonthDay md = date;
|
||||||
|
runAsync(() -> {
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
OfflinePlayer target = Bukkit.getOfflinePlayer(targetName);
|
||||||
|
if (!target.isOnline() && !target.hasPlayedBefore()) {
|
||||||
|
reply(sender, LanguageManager.getString("messages.admin.player_not_found").replace("%player%", targetName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String shownName = target.getName() != null ? target.getName() : targetName;
|
||||||
|
try {
|
||||||
|
switch (action) {
|
||||||
|
case "set" -> {
|
||||||
|
DataHandler.setBirthday(target.getUniqueId(), shownName, md.getMonthValue(), md.getDayOfMonth(), false);
|
||||||
|
reply(sender, LanguageManager.getString("birthday.admin_set")
|
||||||
|
.replace("%player%", shownName).replace("%date%", BirthdayManager.format(md)));
|
||||||
|
}
|
||||||
|
case "remove" -> {
|
||||||
|
boolean removed = DataHandler.removeBirthday(target.getUniqueId(), shownName);
|
||||||
|
reply(sender, LanguageManager.getString(removed ? "birthday.admin_removed" : "birthday.admin_none")
|
||||||
|
.replace("%player%", shownName));
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
boolean reset = DataHandler.resetBirthdayClaim(target.getUniqueId(), shownName);
|
||||||
|
reply(sender, LanguageManager.getString(reset ? "birthday.admin_reset" : "birthday.admin_none")
|
||||||
|
.replace("%player%", shownName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (DataHandler.DataException e) {
|
||||||
|
reply(sender, LanguageManager.getMessage("messages.db_error"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String line : LanguageManager.getStringList("messages.admin.help")) {
|
||||||
|
sender.sendMessage(line);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(LanguageManager.getMessage("messages.only_player"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (!player.hasPermission("adventskalender.use")) {
|
if (!player.hasPermission("adventskalender.use")) {
|
||||||
player.sendMessage(LanguageManager.getMessage("messages.no_permission"));
|
player.sendMessage(LanguageManager.getMessage("messages.no_permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AdventInventory.open(player);
|
// /ak birthday [set <TT.MM> | claim | remove]
|
||||||
|
if (args.length >= 1 && args[0].equalsIgnoreCase("birthday")) {
|
||||||
|
if (!player.hasPermission("adventskalender.birthday")) {
|
||||||
|
player.sendMessage(LanguageManager.getMessage("messages.no_permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (args.length == 1) {
|
||||||
|
BirthdayManager.handleInfo(player);
|
||||||
|
BirthdayManager.sendHelp(player);
|
||||||
|
} else if (args[1].equalsIgnoreCase("set") && args.length == 3) {
|
||||||
|
BirthdayManager.handleSet(player, args[2]);
|
||||||
|
} else if (args[1].equalsIgnoreCase("claim")) {
|
||||||
|
BirthdayManager.handleClaim(player);
|
||||||
|
} else if (args[1].equalsIgnoreCase("remove")) {
|
||||||
|
BirthdayManager.handleRemove(player);
|
||||||
|
} else {
|
||||||
|
BirthdayManager.sendHelp(player);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /ak [Event]
|
||||||
|
CalendarEvent event;
|
||||||
|
if (args.length >= 1) {
|
||||||
|
event = EventManager.get(args[0]);
|
||||||
|
if (event == null) {
|
||||||
|
sendUnknownEvent(player, args[0]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
event = EventManager.defaultEvent();
|
||||||
|
if (event == null) {
|
||||||
|
player.sendMessage(LanguageManager.getMessage("messages.no_events"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdventInventory.open(player, event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendUnknownEvent(CommandSender sender, String given) {
|
||||||
|
String ids = EventManager.allConfigured().stream().map(CalendarEvent::getId).collect(Collectors.joining(", "));
|
||||||
|
sender.sendMessage(LanguageManager.getMessage("messages.unknown_event")
|
||||||
|
.replace("%event%", given).replace("%events%", ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tag zwischen 1 und max, sonst -1. */
|
||||||
|
private static int parseDay(String value, int max) {
|
||||||
|
try {
|
||||||
|
int day = Integer.parseInt(value);
|
||||||
|
return (day >= 1 && day <= max) ? day : -1;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String label(CalendarEvent ev) {
|
||||||
|
return ev != null ? ev.getId() : LanguageManager.getString("messages.admin.all_events");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runAsync(Runnable task) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(Adventskalender.getInstance(), task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sendet die Antwort im Main-Thread. */
|
||||||
|
private void reply(CommandSender sender, String message) {
|
||||||
|
Bukkit.getScheduler().runTask(Adventskalender.getInstance(), () -> sender.sendMessage(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ Tab-Completion
|
||||||
|
|
||||||
|
private static List<String> filter(Collection<String> options, String prefix) {
|
||||||
|
return options.stream()
|
||||||
|
.filter(opt -> opt.toLowerCase().startsWith(prefix.toLowerCase()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> eventIds() {
|
||||||
|
return EventManager.allConfigured().stream().map(CalendarEvent::getId).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> targets() {
|
||||||
|
List<String> options = new ArrayList<>();
|
||||||
|
options.add("global");
|
||||||
|
Bukkit.getOnlinePlayers().forEach(p -> options.add(p.getName()));
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> dayOptions(int max) {
|
||||||
|
List<String> days = new ArrayList<>();
|
||||||
|
for (int i = 1; i <= max; i++) days.add(String.valueOf(i));
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> onTabComplete(CommandSender s, Command c, String a, String[] args) {
|
public List<String> onTabComplete(CommandSender s, Command c, String a, String[] args) {
|
||||||
if (args.length == 1) return Collections.singletonList("admin");
|
boolean admin = s.hasPermission("adventskalender.admin");
|
||||||
|
|
||||||
if (args.length == 2 && args[0].equalsIgnoreCase("admin"))
|
if (args.length == 1) {
|
||||||
return Arrays.asList("reload", "test", "reset", "resetday");
|
List<String> options = new ArrayList<>(EventManager.all().stream().map(CalendarEvent::getId).toList());
|
||||||
|
if (BirthdayManager.isEnabled() && s.hasPermission("adventskalender.birthday")) options.add("birthday");
|
||||||
// Tab-Completion für das Ziel (global oder Spielername)
|
if (admin) options.add("admin");
|
||||||
if (args.length == 3 && (args[1].equalsIgnoreCase("reset") || args[1].equalsIgnoreCase("resetday"))) {
|
return filter(options, args[0]);
|
||||||
List<String> options = new ArrayList<>();
|
|
||||||
options.add("global");
|
|
||||||
Bukkit.getOnlinePlayers().forEach(p -> options.add(p.getName()));
|
|
||||||
return options.stream()
|
|
||||||
.filter(opt -> opt.toLowerCase().startsWith(args[2].toLowerCase()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab-Completion für die TAGE (Numerisch sortiert)
|
if (args[0].equalsIgnoreCase("birthday")) {
|
||||||
if ((args.length == 3 && args[1].equalsIgnoreCase("test")) || (args.length == 4 && args[1].equalsIgnoreCase("resetday"))) {
|
if (args.length == 2) return filter(Arrays.asList("set", "claim", "remove"), args[1]);
|
||||||
List<String> options = new ArrayList<>();
|
return Collections.emptyList();
|
||||||
|
|
||||||
// "reset" Option nur für den test command hinzufügen
|
|
||||||
if (args[1].equalsIgnoreCase("test")) {
|
|
||||||
options.add("reset");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 1; i <= 24; i++) {
|
if (!admin || !args[0].equalsIgnoreCase("admin")) return Collections.emptyList();
|
||||||
options.add(String.valueOf(i));
|
|
||||||
|
if (args.length == 2) return filter(Arrays.asList("reload", "test", "reset", "resetday", "birthday"), args[1]);
|
||||||
|
|
||||||
|
if (args[1].equalsIgnoreCase("birthday")) {
|
||||||
|
if (args.length == 3) return filter(Arrays.asList("set", "remove", "reset"), args[2]);
|
||||||
|
if (args.length == 4) return filter(targets().stream().filter(t -> !t.equals("global")).collect(Collectors.toList()), args[3]);
|
||||||
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
String currentInput = (args.length == 3) ? args[2] : args[3];
|
String sub = args[1].toLowerCase();
|
||||||
|
String last = args[args.length - 1];
|
||||||
|
CalendarEvent ev = EventManager.getAny(args[2]);
|
||||||
|
|
||||||
return options.stream()
|
switch (sub) {
|
||||||
.filter(opt -> opt.toLowerCase().startsWith(currentInput.toLowerCase()))
|
case "test": {
|
||||||
.sorted((s1, s2) -> {
|
if (args.length == 3) {
|
||||||
// "reset" immer nach oben sortieren
|
List<String> options = new ArrayList<>(eventIds());
|
||||||
if (s1.equalsIgnoreCase("reset")) return -1;
|
options.add(0, "reset");
|
||||||
if (s2.equalsIgnoreCase("reset")) return 1;
|
options.addAll(dayOptions(MAX_DAYS));
|
||||||
// Restliche Zahlen numerisch vergleichen
|
return filter(options, last);
|
||||||
return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2));
|
}
|
||||||
})
|
if (args.length == 4 && ev != null) {
|
||||||
.collect(Collectors.toList());
|
List<String> options = new ArrayList<>(dayOptions(ev.getDays()));
|
||||||
|
options.add(0, "reset");
|
||||||
|
return filter(options, last);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "reset": {
|
||||||
|
if (args.length == 3) {
|
||||||
|
List<String> options = new ArrayList<>(eventIds());
|
||||||
|
options.addAll(targets());
|
||||||
|
return filter(options, last);
|
||||||
|
}
|
||||||
|
if (args.length == 4 && ev != null) return filter(targets(), last);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "resetday": {
|
||||||
|
if (args.length == 3) {
|
||||||
|
List<String> options = new ArrayList<>(eventIds());
|
||||||
|
options.addAll(targets());
|
||||||
|
return filter(options, last);
|
||||||
|
}
|
||||||
|
if (args.length == 4) {
|
||||||
|
return ev != null ? filter(targets(), last) : filter(dayOptions(MAX_DAYS), last);
|
||||||
|
}
|
||||||
|
if (args.length == 5 && ev != null) return filter(dayOptions(ev.getDays()), last);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
package de.mviper.adventskalender;
|
package de.mviper.adventskalender;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.enchantments.Enchantment;
|
import org.bukkit.enchantments.Enchantment;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.Inventory;
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.InventoryHolder;
|
||||||
import org.bukkit.inventory.ItemFlag;
|
import org.bukkit.inventory.ItemFlag;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.meta.ItemMeta;
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
import org.bukkit.inventory.meta.SkullMeta;
|
import org.bukkit.inventory.meta.SkullMeta;
|
||||||
import org.bukkit.profile.PlayerProfile;
|
import org.bukkit.profile.PlayerProfile;
|
||||||
import org.bukkit.profile.PlayerTextures;
|
import org.bukkit.profile.PlayerTextures;
|
||||||
import java.net.MalformedURLException;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.Month;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class AdventInventory {
|
public class AdventInventory {
|
||||||
|
|
||||||
@@ -57,28 +58,83 @@ public class AdventInventory {
|
|||||||
// Hinweis: In der Praxis füllst du hier alle 1-24 mit den URLs von Minecraft-Heads.
|
// Hinweis: In der Praxis füllst du hier alle 1-24 mit den URLs von Minecraft-Heads.
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void initCache() {
|
// Spieler, die gerade einen Kalender geöffnet haben
|
||||||
// Cache den Wreath-Kopf
|
private static final Set<UUID> VIEWERS = ConcurrentHashMap.newKeySet();
|
||||||
HEAD_CACHE.put("OPENED", createBaseSkull(OPEN_WREATH_URL));
|
|
||||||
|
|
||||||
// Cache alle nummerierten geschlossenen Köpfe
|
/** Marker, an dem das Kalender-Inventar (und sein Event) erkannt wird statt am Titel. */
|
||||||
|
public static class AdventHolder implements InventoryHolder {
|
||||||
|
private final CalendarEvent event;
|
||||||
|
private Inventory inventory;
|
||||||
|
|
||||||
|
public AdventHolder(CalendarEvent event) { this.event = event; }
|
||||||
|
|
||||||
|
public CalendarEvent getEvent() { return event; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Inventory getInventory() { return inventory; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initCache() {
|
||||||
|
HEAD_CACHE.clear();
|
||||||
|
|
||||||
|
// Advent: Wreath-Kopf und alle nummerierten geschlossenen Köpfe
|
||||||
|
HEAD_CACHE.put("OPENED", createBaseSkull(OPEN_WREATH_URL));
|
||||||
for (int i = 1; i <= 24; i++) {
|
for (int i = 1; i <= 24; i++) {
|
||||||
if (CLOSED_TEXTURES.containsKey(i)) {
|
if (CLOSED_TEXTURES.containsKey(i)) {
|
||||||
HEAD_CACHE.put("CLOSED_" + i, createBaseSkull(CLOSED_TEXTURES.get(i)));
|
HEAD_CACHE.put("CLOSED_" + i, createBaseSkull(CLOSED_TEXTURES.get(i)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Andere Events: optionale eigene Kopf-Texturen aus der Config
|
||||||
|
for (CalendarEvent ev : EventManager.allConfigured()) {
|
||||||
|
if (ev.isAdvent()) continue;
|
||||||
|
if (!ev.getClosedTexture().isBlank()) HEAD_CACHE.put(ev.getId() + "_CLOSED", createBaseSkull(ev.getClosedTexture()));
|
||||||
|
if (!ev.getOpenedTexture().isBlank()) HEAD_CACHE.put(ev.getId() + "_OPENED", createBaseSkull(ev.getOpenedTexture()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void open(Player player) {
|
public static void markClosed(Player player) { VIEWERS.remove(player.getUniqueId()); }
|
||||||
String title = " " + LanguageManager.getString("gui.title");
|
|
||||||
Inventory inv = Bukkit.createInventory(null, 54, title);
|
|
||||||
|
|
||||||
int testDay = Adventskalender.getInstance().getConfig().getInt("general.test_day", 0);
|
public static void closeFor(Player player) {
|
||||||
LocalDate date = LocalDate.now();
|
if (VIEWERS.contains(player.getUniqueId())) player.closeInventory();
|
||||||
int currentDay = (testDay > 0) ? testDay : (date.getMonth() == Month.DECEMBER ? date.getDayOfMonth() : 0);
|
}
|
||||||
|
|
||||||
|
public static void closeAll() {
|
||||||
|
for (UUID id : new ArrayList<>(VIEWERS)) {
|
||||||
|
Player p = Bukkit.getPlayer(id);
|
||||||
|
if (p != null) p.closeInventory();
|
||||||
|
}
|
||||||
|
VIEWERS.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lädt die Daten asynchron und öffnet das Inventar danach im Main-Thread. */
|
||||||
|
public static void open(Player player, CalendarEvent event) {
|
||||||
|
Adventskalender plugin = Adventskalender.getInstance();
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
Set<Integer> claimedDays = DataHandler.getViewClaimedDays(player, event);
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
if (!player.isOnline()) return;
|
||||||
|
if (claimedDays == null) {
|
||||||
|
player.sendMessage(LanguageManager.getMessage("messages.db_error", event));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
build(player, event, claimedDays);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void build(Player player, CalendarEvent event, Set<Integer> claimedDays) {
|
||||||
|
String title = " " + LanguageManager.getString(event.titleKey());
|
||||||
|
AdventHolder holder = new AdventHolder(event);
|
||||||
|
Inventory inv = Bukkit.createInventory(holder, 54, title);
|
||||||
|
holder.inventory = inv;
|
||||||
|
|
||||||
|
int currentDay = event.currentDay();
|
||||||
|
|
||||||
for (int i = 0; i < 54; i++) {
|
for (int i = 0; i < 54; i++) {
|
||||||
if (i < 9 || i >= 45 || i % 9 == 0 || (i + 1) % 9 == 0) {
|
int row = i / 9;
|
||||||
|
boolean border = row == 0 || row == 5 || (event.hasSideBorder() && (i % 9 == 0 || i % 9 == 8));
|
||||||
|
if (border) {
|
||||||
Material m = (i % 2 == 0) ? Material.RED_STAINED_GLASS_PANE : Material.GREEN_STAINED_GLASS_PANE;
|
Material m = (i % 2 == 0) ? Material.RED_STAINED_GLASS_PANE : Material.GREEN_STAINED_GLASS_PANE;
|
||||||
inv.setItem(i, createFiller(m));
|
inv.setItem(i, createFiller(m));
|
||||||
} else {
|
} else {
|
||||||
@@ -86,45 +142,58 @@ public class AdventInventory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] slots = {10, 11, 12, 13, 14, 15, 16, 19, 20, 21, 22, 23, 24, 25, 28, 29, 30, 31, 32, 33, 34, 39, 40, 41};
|
for (int day = 1; day <= event.getDays(); day++) {
|
||||||
|
inv.setItem(event.slotForDay(day), createGiftItem(event, day, claimedDays.contains(day), currentDay >= day));
|
||||||
for (int day = 1; day <= 24; day++) {
|
|
||||||
boolean claimed = DataHandler.hasClaimed(player, day);
|
|
||||||
boolean isAvailable = currentDay >= day;
|
|
||||||
inv.setItem(slots[day - 1], createGiftItem(day, claimed, isAvailable));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
player.openInventory(inv);
|
player.openInventory(inv);
|
||||||
|
// Nach openInventory, da ein vorher offenes Kalender-Inventar dabei geschlossen wird
|
||||||
|
VIEWERS.add(player.getUniqueId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ItemStack createGiftItem(int day, boolean claimed, boolean isAvailable) {
|
private static ItemStack baseItem(CalendarEvent event, int day, boolean claimed) {
|
||||||
ItemStack item;
|
if (event.isAdvent()) {
|
||||||
if (claimed) {
|
return claimed
|
||||||
item = HEAD_CACHE.getOrDefault("OPENED", new ItemStack(Material.PLAYER_HEAD)).clone();
|
? HEAD_CACHE.getOrDefault("OPENED", new ItemStack(Material.PLAYER_HEAD)).clone()
|
||||||
} else {
|
: HEAD_CACHE.getOrDefault("CLOSED_" + day, new ItemStack(Material.PLAYER_HEAD)).clone();
|
||||||
item = HEAD_CACHE.getOrDefault("CLOSED_" + day, new ItemStack(Material.PLAYER_HEAD)).clone();
|
|
||||||
}
|
}
|
||||||
|
ItemStack head = HEAD_CACHE.get(event.getId() + (claimed ? "_OPENED" : "_CLOSED"));
|
||||||
|
ItemStack item = head != null ? head.clone() : new ItemStack(claimed ? event.getOpenedMaterial() : event.getClosedMaterial());
|
||||||
|
item.setAmount(Math.max(1, Math.min(64, day))); // Die Stackgröße zeigt den Tag an
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ItemStack createGiftItem(CalendarEvent event, int day, boolean claimed, boolean isAvailable) {
|
||||||
|
ItemStack item = baseItem(event, day, claimed);
|
||||||
|
|
||||||
ItemMeta meta = item.getItemMeta();
|
ItemMeta meta = item.getItemMeta();
|
||||||
List<String> lore = new ArrayList<>();
|
List<String> lore = new ArrayList<>();
|
||||||
lore.add(" ");
|
lore.add(" ");
|
||||||
|
String dayStr = String.valueOf(day);
|
||||||
|
|
||||||
|
String rewardName = LanguageManager.has(event.nameKey(day))
|
||||||
|
? LanguageManager.getString(event.nameKey(day))
|
||||||
|
: LanguageManager.getString("gui.default_name").replace("%day%", dayStr);
|
||||||
|
|
||||||
if (claimed) {
|
if (claimed) {
|
||||||
meta.setDisplayName(LanguageManager.getString("rewards.day_" + day + ".name"));
|
meta.setDisplayName(rewardName);
|
||||||
meta.addEnchant(Enchantment.LUCK, 1, true);
|
// Glitzer-Effekt über eine beliebige Verzauberung, die per Key gesucht wird (versionsunabhängig)
|
||||||
|
Enchantment glow = Enchantment.getByKey(NamespacedKey.minecraft("luck_of_the_sea"));
|
||||||
|
if (glow != null) {
|
||||||
|
meta.addEnchant(glow, 1, true);
|
||||||
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||||
lore.add("§8» §6Bereits abgeholt");
|
|
||||||
lore.add("§aDu hast dieses Geschenk bereits geöffnet! ✔");
|
|
||||||
} else {
|
|
||||||
if (!isAvailable) {
|
|
||||||
meta.setDisplayName("§c§lTag #" + day);
|
|
||||||
lore.add("§8» §7Noch verschlossen");
|
|
||||||
lore.add("§7Komme am " + day + ". Dezember wieder!");
|
|
||||||
} else {
|
|
||||||
meta.setDisplayName(LanguageManager.getString("rewards.day_" + day + ".name"));
|
|
||||||
lore.add("§8» §eKlicke zum Öffnen!");
|
|
||||||
lore.addAll(LanguageManager.getStringList("rewards.day_" + day + ".lore"));
|
|
||||||
}
|
}
|
||||||
|
lore.add(LanguageManager.getString("gui.claimed_status"));
|
||||||
|
lore.add(LanguageManager.getString("gui.claimed_hint"));
|
||||||
|
} else if (!isAvailable) {
|
||||||
|
meta.setDisplayName(LanguageManager.getString("gui.locked_name").replace("%day%", dayStr));
|
||||||
|
lore.add(LanguageManager.getString("gui.locked_status"));
|
||||||
|
lore.add(LanguageManager.getString("gui.locked_hint")
|
||||||
|
.replace("%day%", dayStr).replace("%date%", event.formatDate(day)));
|
||||||
|
} else {
|
||||||
|
meta.setDisplayName(rewardName);
|
||||||
|
lore.add(LanguageManager.getString("gui.click_open"));
|
||||||
|
lore.addAll(LanguageManager.getStringList(event.loreKey(day)));
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.setLore(lore);
|
meta.setLore(lore);
|
||||||
@@ -136,12 +205,16 @@ public class AdventInventory {
|
|||||||
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
|
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
|
||||||
SkullMeta meta = (SkullMeta) item.getItemMeta();
|
SkullMeta meta = (SkullMeta) item.getItemMeta();
|
||||||
try {
|
try {
|
||||||
UUID uuid = UUID.nameUUIDFromBytes(url.getBytes());
|
UUID uuid = UUID.nameUUIDFromBytes(url.getBytes(StandardCharsets.UTF_8));
|
||||||
PlayerProfile profile = Bukkit.createPlayerProfile(uuid, "AdventHead");
|
PlayerProfile profile = Bukkit.createPlayerProfile(uuid, "AdventHead");
|
||||||
profile.getTextures().setSkin(new URL(url));
|
PlayerTextures textures = profile.getTextures();
|
||||||
|
textures.setSkin(URI.create(url).toURL());
|
||||||
|
profile.setTextures(textures);
|
||||||
meta.setOwnerProfile(profile);
|
meta.setOwnerProfile(profile);
|
||||||
item.setItemMeta(meta);
|
item.setItemMeta(meta);
|
||||||
} catch (MalformedURLException e) { e.printStackTrace(); }
|
} catch (Exception e) {
|
||||||
|
Adventskalender.getInstance().getLogger().warning("Kopf-Textur konnte nicht geladen werden (" + url + "): " + e.getMessage());
|
||||||
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,32 +5,39 @@ import net.md_5.bungee.api.chat.HoverEvent;
|
|||||||
import net.md_5.bungee.api.chat.TextComponent;
|
import net.md_5.bungee.api.chat.TextComponent;
|
||||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.ChatColor;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.enchantments.Enchantment;
|
|
||||||
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;
|
||||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.meta.ItemMeta;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.Month;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class AdventListener implements Listener {
|
public class AdventListener implements Listener {
|
||||||
|
|
||||||
|
// Spieler, deren Klick gerade verarbeitet wird (verhindert Spam-Klicks)
|
||||||
|
private final Set<UUID> busy = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onJoin(PlayerJoinEvent event) {
|
public void onJoin(PlayerJoinEvent event) {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
// Hinweis auf das Geburtstagsgeschenk (falls abholbar)
|
||||||
|
Bukkit.getScheduler().runTaskLater(Adventskalender.getInstance(), () -> {
|
||||||
|
if (player.isOnline()) BirthdayManager.checkOnJoin(player);
|
||||||
|
}, 80L);
|
||||||
|
|
||||||
// Nachricht an Admins senden, wenn ein Update da ist
|
// Nachricht an Admins senden, wenn ein Update da ist
|
||||||
if (player.hasPermission("adventskalender.admin")) {
|
if (player.hasPermission("adventskalender.admin")) {
|
||||||
// Delay von 2 Sekunden (40 Ticks), damit die Nachricht nicht im Join-Spam untergeht
|
// Delay von 2 Sekunden (40 Ticks), damit die Nachricht nicht im Join-Spam untergeht
|
||||||
Bukkit.getScheduler().runTaskLater(Adventskalender.getInstance(), () -> {
|
Bukkit.getScheduler().runTaskLater(Adventskalender.getInstance(), () -> {
|
||||||
|
if (!player.isOnline()) return;
|
||||||
if (Adventskalender.getInstance().isUpdateAvailable()) {
|
if (Adventskalender.getInstance().isUpdateAvailable()) {
|
||||||
String latest = Adventskalender.getInstance().getLatestVersion();
|
String latest = Adventskalender.getInstance().getLatestVersion();
|
||||||
String current = Adventskalender.getInstance().getDescription().getVersion();
|
String current = Adventskalender.getInstance().getDescription().getVersion();
|
||||||
@@ -58,78 +65,99 @@ public class AdventListener implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryClose(InventoryCloseEvent event) {
|
||||||
|
if (event.getInventory().getHolder() instanceof AdventInventory.AdventHolder
|
||||||
|
&& event.getPlayer() instanceof Player player) {
|
||||||
|
AdventInventory.markClosed(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onInventoryDrag(InventoryDragEvent event) {
|
||||||
|
if (event.getInventory().getHolder() instanceof AdventInventory.AdventHolder) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onInventoryClick(InventoryClickEvent event) {
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
String baseTitle = LanguageManager.getString("gui.title");
|
if (!(event.getInventory().getHolder() instanceof AdventInventory.AdventHolder)) return;
|
||||||
if (!event.getView().getTitle().contains(baseTitle)) return;
|
|
||||||
|
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
|
|
||||||
if (!(event.getWhoClicked() instanceof Player player)) return;
|
if (!(event.getWhoClicked() instanceof Player player)) return;
|
||||||
|
if (event.getClickedInventory() != event.getInventory()) return;
|
||||||
|
|
||||||
ItemStack clicked = event.getCurrentItem();
|
CalendarEvent calendar = ((AdventInventory.AdventHolder) event.getInventory().getHolder()).getEvent();
|
||||||
if (clicked == null || clicked.getType() != Material.PLAYER_HEAD) return;
|
int day = calendar.dayForSlot(event.getRawSlot());
|
||||||
if (!clicked.hasItemMeta() || !clicked.getItemMeta().hasDisplayName()) return;
|
if (day == -1) return;
|
||||||
|
|
||||||
String displayName = ChatColor.stripColor(clicked.getItemMeta().getDisplayName());
|
Adventskalender plugin = Adventskalender.getInstance();
|
||||||
int day;
|
|
||||||
try {
|
|
||||||
day = Integer.parseInt(displayName.replaceAll("[^0-9]", ""));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int testDay = Adventskalender.getInstance().getConfig().getInt("general.test_day", 0);
|
if (calendar.currentDay() < day) {
|
||||||
LocalDate date = LocalDate.now();
|
player.sendMessage(LanguageManager.getMessage("messages.day_not_available", calendar));
|
||||||
int currentDay = (testDay > 0) ? testDay : (date.getMonth() == Month.DECEMBER ? date.getDayOfMonth() : 0);
|
|
||||||
|
|
||||||
if (currentDay >= day) {
|
|
||||||
if (DataHandler.hasClaimed(player, day)) {
|
|
||||||
player.sendMessage(LanguageManager.getMessage("messages.day_already_claimed"));
|
|
||||||
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
giveReward(player, day);
|
|
||||||
DataHandler.setClaimed(player, day);
|
|
||||||
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.5f);
|
|
||||||
|
|
||||||
AdventInventory.open(player);
|
|
||||||
} else {
|
|
||||||
player.sendMessage(LanguageManager.getMessage("messages.day_not_available"));
|
|
||||||
player.playSound(player.getLocation(), Sound.BLOCK_CHEST_LOCKED, 1f, 1f);
|
player.playSound(player.getLocation(), Sound.BLOCK_CHEST_LOCKED, 1f, 1f);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void giveReward(Player player, int day) {
|
// Belohnung vorab prüfen: Bei Konfigurationsfehlern wird der Tag nicht verbraucht
|
||||||
String path = "rewards." + day;
|
ItemStack reward = buildReward(calendar, day);
|
||||||
String matName = Adventskalender.getInstance().getConfig().getString(path + ".material", "COOKIE");
|
if (reward == null) {
|
||||||
int amount = Adventskalender.getInstance().getConfig().getInt(path + ".amount", 1);
|
player.sendMessage(LanguageManager.getMessage("messages.reward_error", calendar).replace("%day%", String.valueOf(day)));
|
||||||
|
return;
|
||||||
ItemStack reward = new ItemStack(Material.valueOf(matName.toUpperCase()), amount);
|
|
||||||
ItemMeta meta = reward.getItemMeta();
|
|
||||||
|
|
||||||
if (meta != null) {
|
|
||||||
List<Map<?, ?>> enchants = Adventskalender.getInstance().getConfig().getMapList(path + ".enchantments");
|
|
||||||
for (Map<?, ?> entry : enchants) {
|
|
||||||
String type = (String) entry.get("type");
|
|
||||||
Object levelObj = entry.get("level");
|
|
||||||
int level = (levelObj instanceof Integer) ? (int) levelObj : 1;
|
|
||||||
|
|
||||||
Enchantment enc = Enchantment.getByName(type.toUpperCase());
|
|
||||||
if (enc != null) {
|
|
||||||
meta.addEnchant(enc, level, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reward.setItemMeta(meta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.getInventory().firstEmpty() == -1) {
|
UUID id = player.getUniqueId();
|
||||||
player.getWorld().dropItemNaturally(player.getLocation(), reward);
|
if (!busy.add(id)) return;
|
||||||
player.sendMessage(LanguageManager.getMessage("messages.inventory_full"));
|
|
||||||
|
// Datenbankzugriff async, Ergebnis wieder im Main-Thread verarbeiten
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
DataHandler.ClaimResult result = DataHandler.isReady()
|
||||||
|
? DataHandler.setClaimed(player, calendar, day)
|
||||||
|
: DataHandler.ClaimResult.ERROR;
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
busy.remove(id);
|
||||||
|
|
||||||
|
if (result == DataHandler.ClaimResult.SUCCESS && !player.isOnline()) {
|
||||||
|
// Spieler ist inzwischen weg: Tag wieder freigeben, damit die Belohnung nicht verloren geht
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
if (calendar.isGlobal()) {
|
||||||
|
DataHandler.resetGlobalDay(calendar, day);
|
||||||
} else {
|
} else {
|
||||||
player.getInventory().addItem(reward);
|
DataHandler.resetPlayerDay(player.getName(), calendar, day);
|
||||||
}
|
}
|
||||||
player.sendMessage(LanguageManager.getMessage("messages.reward_received").replace("%day%", String.valueOf(day)));
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!player.isOnline()) return;
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case SUCCESS -> {
|
||||||
|
giveReward(player, calendar, day, reward);
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.5f);
|
||||||
|
AdventInventory.open(player, calendar);
|
||||||
|
}
|
||||||
|
case ALREADY_CLAIMED -> {
|
||||||
|
player.sendMessage(LanguageManager.getMessage(calendar.isGlobal() ? "messages.global_already_claimed" : "messages.day_already_claimed", calendar));
|
||||||
|
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f);
|
||||||
|
}
|
||||||
|
case ERROR -> player.sendMessage(LanguageManager.getMessage("messages.db_error", calendar));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Baut das Belohnungs-Item aus der Config. Gibt null zurück, wenn das Material ungültig ist. */
|
||||||
|
private ItemStack buildReward(CalendarEvent calendar, int day) {
|
||||||
|
return RewardUtil.build(calendar.rewardsPath() + "." + day, "Tag " + day + " (" + calendar.getId() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void giveReward(Player player, CalendarEvent calendar, int day, ItemStack reward) {
|
||||||
|
if (RewardUtil.give(player, reward)) {
|
||||||
|
player.sendMessage(LanguageManager.getMessage("messages.inventory_full", calendar));
|
||||||
|
}
|
||||||
|
player.sendMessage(LanguageManager.getMessage("messages.reward_received", calendar).replace("%day%", String.valueOf(day)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,16 @@ import org.bukkit.Bukkit;
|
|||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.time.DateTimeException;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
|
||||||
public final class Adventskalender extends JavaPlugin {
|
public final class Adventskalender extends JavaPlugin {
|
||||||
private static Adventskalender instance;
|
private static Adventskalender instance;
|
||||||
private NamespacedKey dataKey;
|
private NamespacedKey dataKey;
|
||||||
|
|
||||||
// Update-Status Variablen
|
// Update-Status Variablen
|
||||||
private boolean updateAvailable = false;
|
private volatile boolean updateAvailable = false;
|
||||||
private String latestVersion = "";
|
private volatile String latestVersion = "";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
@@ -18,6 +21,7 @@ public final class Adventskalender extends JavaPlugin {
|
|||||||
this.dataKey = new NamespacedKey(this, "claimed_days_v2");
|
this.dataKey = new NamespacedKey(this, "claimed_days_v2");
|
||||||
|
|
||||||
saveDefaultConfig();
|
saveDefaultConfig();
|
||||||
|
reloadSettings();
|
||||||
LanguageManager.setup();
|
LanguageManager.setup();
|
||||||
|
|
||||||
// Initialisierung der Daten (MySQL wird hier bevorzugt behandelt)
|
// Initialisierung der Daten (MySQL wird hier bevorzugt behandelt)
|
||||||
@@ -31,7 +35,8 @@ public final class Adventskalender extends JavaPlugin {
|
|||||||
|
|
||||||
// Update Checker Integration (ID: 130974)
|
// Update Checker Integration (ID: 130974)
|
||||||
new UpdateChecker(this, 130974).getVersion(version -> {
|
new UpdateChecker(this, 130974).getVersion(version -> {
|
||||||
if (!this.getDescription().getVersion().equals(version)) {
|
String current = this.getDescription().getVersion();
|
||||||
|
if (isNewer(version, current)) {
|
||||||
this.updateAvailable = true;
|
this.updateAvailable = true;
|
||||||
this.latestVersion = version;
|
this.latestVersion = version;
|
||||||
getLogger().warning("Eine neue Version (" + version + ") ist verfügbar!");
|
getLogger().warning("Eine neue Version (" + version + ") ist verfügbar!");
|
||||||
@@ -53,6 +58,42 @@ public final class Adventskalender extends JavaPlugin {
|
|||||||
MySQLManager.close();
|
MySQLManager.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Liest die Werte, die aus der Config stammen, neu ein (auch nach /ak admin reload). */
|
||||||
|
public void reloadSettings() {
|
||||||
|
EventManager.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoneId getZone() {
|
||||||
|
String tz = getConfig().getString("general.timezone", "");
|
||||||
|
if (tz != null && !tz.isBlank()) {
|
||||||
|
try {
|
||||||
|
return ZoneId.of(tz.trim());
|
||||||
|
} catch (DateTimeException e) {
|
||||||
|
getLogger().warning("Ungültige Zeitzone '" + tz + "' in der config.yml - nutze Server-Zeitzone.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ZoneId.systemDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** true, wenn die entfernte Version numerisch neuer ist als die lokale. */
|
||||||
|
private static boolean isNewer(String remote, String local) {
|
||||||
|
String[] r = remote.split("\\.");
|
||||||
|
String[] l = local.split("\\.");
|
||||||
|
for (int i = 0; i < Math.max(r.length, l.length); i++) {
|
||||||
|
int a = part(r, i);
|
||||||
|
int b = part(l, i);
|
||||||
|
if (a != b) return a > b;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int part(String[] parts, int i) {
|
||||||
|
if (i >= parts.length) return 0;
|
||||||
|
String digits = parts[i].replaceAll("[^0-9]", "");
|
||||||
|
if (digits.isEmpty() || digits.length() > 9) return 0;
|
||||||
|
return Integer.parseInt(digits);
|
||||||
|
}
|
||||||
|
|
||||||
public static Adventskalender getInstance() { return instance; }
|
public static Adventskalender getInstance() { return instance; }
|
||||||
public NamespacedKey getDataKey() { return dataKey; }
|
public NamespacedKey getDataKey() { return dataKey; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,289 @@
|
|||||||
|
package de.mviper.adventskalender;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.chat.ClickEvent;
|
||||||
|
import net.md_5.bungee.api.chat.HoverEvent;
|
||||||
|
import net.md_5.bungee.api.chat.TextComponent;
|
||||||
|
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
import java.time.DateTimeException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.MonthDay;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spieler-Geburtstage: Der Spieler trägt sein Datum (ohne Jahr) ein und kann am Geburtstag
|
||||||
|
* sowie bis zu birthday.grace_days Tage danach einmal pro Jahr ein Geschenk abholen.
|
||||||
|
*/
|
||||||
|
public final class BirthdayManager {
|
||||||
|
|
||||||
|
private BirthdayManager() {}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ Config / Datum
|
||||||
|
|
||||||
|
public static boolean isEnabled() {
|
||||||
|
return Adventskalender.getInstance().getConfig().getBoolean("birthday.enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int graceDays() {
|
||||||
|
return Math.max(0, Adventskalender.getInstance().getConfig().getInt("birthday.grace_days", 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean allowChange() {
|
||||||
|
return Adventskalender.getInstance().getConfig().getBoolean("birthday.allow_change", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Erwartet TT.MM (auch 5.3 oder 05.03.). Gibt null bei ungültigen Daten zurück. */
|
||||||
|
public static MonthDay parse(String input) {
|
||||||
|
if (input == null) return null;
|
||||||
|
String s = input.trim();
|
||||||
|
if (s.endsWith(".")) s = s.substring(0, s.length() - 1);
|
||||||
|
String[] parts = s.split("\\.");
|
||||||
|
if (parts.length != 2) return null;
|
||||||
|
try {
|
||||||
|
return MonthDay.of(Integer.parseInt(parts[1].trim()), Integer.parseInt(parts[0].trim()));
|
||||||
|
} catch (NumberFormatException | DateTimeException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String format(int month, int day) {
|
||||||
|
return String.format("%02d.%02d.", day, month);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String format(MonthDay md) {
|
||||||
|
return format(md.getMonthValue(), md.getDayOfMonth());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das Jahr des Geburtstags, dessen Zeitfenster (Geburtstag bis +grace_days) heute läuft, sonst null.
|
||||||
|
* Berücksichtigt den Jahreswechsel (z.B. 31.12. + 2 Tage). Der 29.2. wird in Nichtschaltjahren zum 28.2.
|
||||||
|
*/
|
||||||
|
public static Integer activeYear(int month, int day, LocalDate today) {
|
||||||
|
MonthDay md = MonthDay.of(month, day);
|
||||||
|
for (int y = today.getYear() - 1; y <= today.getYear(); y++) {
|
||||||
|
LocalDate start = md.atYear(y);
|
||||||
|
if (!today.isBefore(start) && !today.isAfter(start.plusDays(graceDays()))) return y;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String msg(String path) {
|
||||||
|
return LanguageManager.getMessage(path, "birthday.prefix");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LocalDate today() {
|
||||||
|
return LocalDate.now(Adventskalender.getInstance().getZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void async(Runnable task) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(Adventskalender.getInstance(), task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sync(Runnable task) {
|
||||||
|
Bukkit.getScheduler().runTask(Adventskalender.getInstance(), task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkEnabled(Player player) {
|
||||||
|
if (isEnabled()) return true;
|
||||||
|
player.sendMessage(msg("birthday.disabled"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ Spieler-Aktionen
|
||||||
|
|
||||||
|
public static void sendHelp(Player player) {
|
||||||
|
for (String line : LanguageManager.getStringList("birthday.help")) {
|
||||||
|
player.sendMessage(line.replace("%days%", String.valueOf(graceDays())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handleInfo(Player player) {
|
||||||
|
if (!checkEnabled(player)) return;
|
||||||
|
UUID id = player.getUniqueId();
|
||||||
|
async(() -> {
|
||||||
|
try {
|
||||||
|
Optional<DataHandler.Birthday> b = DataHandler.getBirthday(id);
|
||||||
|
sync(() -> {
|
||||||
|
if (b.isPresent()) {
|
||||||
|
player.sendMessage(msg("birthday.info")
|
||||||
|
.replace("%date%", format(b.get().month(), b.get().day())));
|
||||||
|
} else {
|
||||||
|
player.sendMessage(msg("birthday.info_none"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (DataHandler.DataException e) {
|
||||||
|
sync(() -> player.sendMessage(msg("messages.db_error")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handleSet(Player player, String input) {
|
||||||
|
if (!checkEnabled(player)) return;
|
||||||
|
MonthDay md = parse(input);
|
||||||
|
if (md == null) {
|
||||||
|
player.sendMessage(msg("birthday.invalid_date"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UUID id = player.getUniqueId();
|
||||||
|
String name = player.getName();
|
||||||
|
async(() -> {
|
||||||
|
try {
|
||||||
|
boolean written = DataHandler.setBirthday(id, name, md.getMonthValue(), md.getDayOfMonth(), !allowChange());
|
||||||
|
Optional<DataHandler.Birthday> current = written ? Optional.empty() : DataHandler.getBirthday(id);
|
||||||
|
sync(() -> {
|
||||||
|
if (written) {
|
||||||
|
player.sendMessage(msg("birthday.set_success")
|
||||||
|
.replace("%date%", format(md)).replace("%days%", String.valueOf(graceDays())));
|
||||||
|
} else {
|
||||||
|
player.sendMessage(msg("birthday.already_set")
|
||||||
|
.replace("%date%", current.map(b -> format(b.month(), b.day())).orElse("?")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (DataHandler.DataException e) {
|
||||||
|
sync(() -> player.sendMessage(msg("messages.db_error")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handleRemove(Player player) {
|
||||||
|
if (!checkEnabled(player)) return;
|
||||||
|
if (!allowChange()) {
|
||||||
|
player.sendMessage(msg("birthday.change_not_allowed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UUID id = player.getUniqueId();
|
||||||
|
async(() -> {
|
||||||
|
try {
|
||||||
|
boolean removed = DataHandler.removeBirthday(id, null);
|
||||||
|
sync(() -> player.sendMessage(msg(removed ? "birthday.removed" : "birthday.info_none")));
|
||||||
|
} catch (DataHandler.DataException e) {
|
||||||
|
sync(() -> player.sendMessage(msg("messages.db_error")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handleClaim(Player player) {
|
||||||
|
if (!checkEnabled(player)) return;
|
||||||
|
|
||||||
|
// Belohnungen vorab bauen: Bei Konfigurationsfehlern wird nichts verbraucht
|
||||||
|
List<ItemStack> items = new ArrayList<>();
|
||||||
|
ConfigurationSection rewards = Adventskalender.getInstance().getConfig().getConfigurationSection("birthday.rewards");
|
||||||
|
if (rewards != null) {
|
||||||
|
for (String key : rewards.getKeys(false)) {
|
||||||
|
ItemStack item = RewardUtil.build("birthday.rewards." + key, "Geburtstags-Belohnung " + key);
|
||||||
|
if (item == null) {
|
||||||
|
player.sendMessage(msg("birthday.reward_error"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> commands = Adventskalender.getInstance().getConfig().getStringList("birthday.commands");
|
||||||
|
if (items.isEmpty() && commands.isEmpty()) {
|
||||||
|
player.sendMessage(msg("birthday.reward_error"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID id = player.getUniqueId();
|
||||||
|
String name = player.getName();
|
||||||
|
LocalDate today = today();
|
||||||
|
|
||||||
|
async(() -> {
|
||||||
|
String messageKey = null;
|
||||||
|
DataHandler.ClaimResult result = null;
|
||||||
|
Integer year = null;
|
||||||
|
try {
|
||||||
|
Optional<DataHandler.Birthday> b = DataHandler.getBirthday(id);
|
||||||
|
if (b.isEmpty()) {
|
||||||
|
messageKey = "birthday.not_set";
|
||||||
|
} else {
|
||||||
|
year = activeYear(b.get().month(), b.get().day(), today);
|
||||||
|
if (year == null) {
|
||||||
|
String date = format(b.get().month(), b.get().day());
|
||||||
|
sync(() -> player.sendMessage(msg("birthday.not_today")
|
||||||
|
.replace("%date%", date).replace("%days%", String.valueOf(graceDays()))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result = DataHandler.claimBirthday(id, year);
|
||||||
|
}
|
||||||
|
} catch (DataHandler.DataException e) {
|
||||||
|
result = DataHandler.ClaimResult.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String key = messageKey;
|
||||||
|
final DataHandler.ClaimResult res = result;
|
||||||
|
sync(() -> {
|
||||||
|
if (key != null) {
|
||||||
|
player.sendMessage(msg(key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (res == DataHandler.ClaimResult.SUCCESS && !player.isOnline()) {
|
||||||
|
// Spieler ist inzwischen weg: Abholung zurücknehmen, damit das Geschenk nicht verloren geht
|
||||||
|
async(() -> DataHandler.resetBirthdayClaim(id, null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!player.isOnline()) return;
|
||||||
|
switch (res) {
|
||||||
|
case SUCCESS -> giveRewards(player, items, commands);
|
||||||
|
case ALREADY_CLAIMED -> player.sendMessage(msg("birthday.already_claimed"));
|
||||||
|
default -> player.sendMessage(msg("messages.db_error"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void giveRewards(Player player, List<ItemStack> items, List<String> commands) {
|
||||||
|
boolean dropped = false;
|
||||||
|
for (ItemStack item : items) {
|
||||||
|
dropped |= RewardUtil.give(player, item);
|
||||||
|
}
|
||||||
|
for (String command : commands) {
|
||||||
|
String cmd = command.startsWith("/") ? command.substring(1) : command;
|
||||||
|
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd.replace("%player%", player.getName()));
|
||||||
|
}
|
||||||
|
if (dropped) player.sendMessage(msg("messages.inventory_full"));
|
||||||
|
player.sendMessage(msg("birthday.reward_received"));
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
|
||||||
|
|
||||||
|
if (Adventskalender.getInstance().getConfig().getBoolean("birthday.broadcast", true)) {
|
||||||
|
Bukkit.broadcastMessage(msg("birthday.broadcast").replace("%player%", player.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ Join-Hinweis
|
||||||
|
|
||||||
|
/** Weist beim Beitritt auf das Geschenk hin, wenn es gerade abholbar ist. */
|
||||||
|
public static void checkOnJoin(Player player) {
|
||||||
|
if (!isEnabled() || !player.hasPermission("adventskalender.birthday")) return;
|
||||||
|
UUID id = player.getUniqueId();
|
||||||
|
LocalDate today = today();
|
||||||
|
async(() -> {
|
||||||
|
try {
|
||||||
|
Optional<DataHandler.Birthday> b = DataHandler.getBirthday(id);
|
||||||
|
if (b.isEmpty()) return;
|
||||||
|
Integer year = activeYear(b.get().month(), b.get().day(), today);
|
||||||
|
if (year == null || b.get().claimedYear() >= year) return;
|
||||||
|
|
||||||
|
sync(() -> {
|
||||||
|
if (!player.isOnline()) return;
|
||||||
|
TextComponent message = new TextComponent(
|
||||||
|
msg("birthday.join_notice"));
|
||||||
|
message.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/adventskalender birthday claim"));
|
||||||
|
message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||||
|
new Text(LanguageManager.getString("birthday.join_hover"))));
|
||||||
|
player.spigot().sendMessage(message);
|
||||||
|
});
|
||||||
|
} catch (DataHandler.DataException ignored) {
|
||||||
|
// Beim Beitritt keine Fehlermeldung an den Spieler
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package de.mviper.adventskalender;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.MonthDay;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein Kalender-Event (z.B. Advent, Halloween, Ostern), gelesen aus dem Abschnitt events.<id> der config.yml.
|
||||||
|
*/
|
||||||
|
public final class CalendarEvent {
|
||||||
|
|
||||||
|
public static final String ADVENT = "advent";
|
||||||
|
private static final Pattern EASTER = Pattern.compile("(?i)easter\\s*([+-]\\s*\\d+)?");
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final boolean enabled;
|
||||||
|
private final int days;
|
||||||
|
private final int graceDays;
|
||||||
|
private final Boolean globalOverride;
|
||||||
|
private final MonthDay fixedStart;
|
||||||
|
private final Integer easterOffset;
|
||||||
|
private final Material closedMaterial;
|
||||||
|
private final Material openedMaterial;
|
||||||
|
private final String closedTexture;
|
||||||
|
private final String openedTexture;
|
||||||
|
private final int perRow;
|
||||||
|
private final int[] slots;
|
||||||
|
private volatile int testDay;
|
||||||
|
|
||||||
|
public CalendarEvent(String id, FileConfiguration cfg, int testDay) {
|
||||||
|
this.id = id.toLowerCase();
|
||||||
|
String p = "events." + id + ".";
|
||||||
|
|
||||||
|
this.enabled = cfg.getBoolean(p + "enabled", true);
|
||||||
|
this.days = Math.max(1, Math.min(36, cfg.getInt(p + "days", 24)));
|
||||||
|
this.graceDays = Math.max(0, cfg.getInt(p + "grace_days", 0));
|
||||||
|
Object g = cfg.get(p + "global");
|
||||||
|
this.globalOverride = (g instanceof Boolean b) ? b : null;
|
||||||
|
this.closedMaterial = material(cfg.getString(p + "closed_material", "CHEST"), Material.CHEST);
|
||||||
|
this.openedMaterial = material(cfg.getString(p + "opened_material", "ENDER_CHEST"), Material.ENDER_CHEST);
|
||||||
|
this.closedTexture = cfg.getString(p + "closed_texture", "");
|
||||||
|
this.openedTexture = cfg.getString(p + "opened_texture", "");
|
||||||
|
this.testDay = testDay;
|
||||||
|
|
||||||
|
String start = cfg.getString(p + "start", "12-01").trim();
|
||||||
|
Matcher m = EASTER.matcher(start);
|
||||||
|
if (m.matches()) {
|
||||||
|
this.fixedStart = null;
|
||||||
|
this.easterOffset = m.group(1) == null ? 0 : Integer.parseInt(m.group(1).replaceAll("\\s", ""));
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
this.fixedStart = MonthDay.parse("--" + start);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("Ungültiger Startwert '" + start + "' (erwartet MM-dd oder easter-13)");
|
||||||
|
}
|
||||||
|
this.easterOffset = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slots: bis 28 Tage mit Rand links/rechts (7 pro Reihe), darüber 9 pro Reihe. Letzte Reihe zentriert.
|
||||||
|
this.perRow = days <= 28 ? 7 : 9;
|
||||||
|
int colStart = perRow == 7 ? 1 : 0;
|
||||||
|
this.slots = new int[days];
|
||||||
|
int rows = (days + perRow - 1) / perRow;
|
||||||
|
int d = 0;
|
||||||
|
for (int r = 0; r < rows; r++) {
|
||||||
|
int n = Math.min(perRow, days - d);
|
||||||
|
int offset = (perRow - n) / 2;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
slots[d++] = (1 + r) * 9 + colStart + offset + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Material material(String name, Material fallback) {
|
||||||
|
Material m = name == null ? null : Material.matchMaterial(name);
|
||||||
|
return (m != null && m.isItem() && !m.isAir()) ? m : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------- Datum
|
||||||
|
|
||||||
|
/** Ostersonntag (Gaußsche Osterformel, gregorianisch). */
|
||||||
|
public static LocalDate easter(int year) {
|
||||||
|
int a = year % 19, b = year / 100, c = year % 100;
|
||||||
|
int d = b / 4, e = b % 4, f = (b + 8) / 25, g = (b - f + 1) / 3;
|
||||||
|
int h = (19 * a + b - d - g + 15) % 30;
|
||||||
|
int i = c / 4, k = c % 4;
|
||||||
|
int l = (32 + 2 * e + 2 * i - h - k) % 7;
|
||||||
|
int m = (a + 11 * h + 22 * l) / 451;
|
||||||
|
int month = (h + l - 7 * m + 114) / 31;
|
||||||
|
int day = ((h + l - 7 * m + 114) % 31) + 1;
|
||||||
|
return LocalDate.of(year, month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDate startFor(int year) {
|
||||||
|
return easterOffset != null ? easter(year).plusDays(easterOffset) : fixedStart.atYear(year);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Start des laufenden oder des nächsten Zeitfensters. */
|
||||||
|
private LocalDate relevantStart(LocalDate today) {
|
||||||
|
for (int y = today.getYear() - 1; y <= today.getYear() + 1; y++) {
|
||||||
|
LocalDate s = startFor(y);
|
||||||
|
if (today.isBefore(s.plusDays(days + graceDays))) return s;
|
||||||
|
}
|
||||||
|
return startFor(today.getYear() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Aktueller Tag (1..days), oder 0 außerhalb des Zeitraums. Berücksichtigt den Testmodus. */
|
||||||
|
public int currentDay() {
|
||||||
|
if (testDay > 0) return Math.min(days, testDay);
|
||||||
|
LocalDate today = LocalDate.now(Adventskalender.getInstance().getZone());
|
||||||
|
long offset = ChronoUnit.DAYS.between(relevantStart(today), today);
|
||||||
|
if (offset < 0) return 0;
|
||||||
|
return (int) Math.min(days, offset + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Kalenderdatum eines Türchens, formatiert für die Sprache aus der Config. */
|
||||||
|
public String formatDate(int day) {
|
||||||
|
LocalDate today = LocalDate.now(Adventskalender.getInstance().getZone());
|
||||||
|
LocalDate date = relevantStart(today).plusDays(day - 1);
|
||||||
|
String lang = Adventskalender.getInstance().getConfig().getString("general.language", "de");
|
||||||
|
Locale locale = Locale.forLanguageTag(lang);
|
||||||
|
return date.format(DateTimeFormatter.ofPattern(lang.equalsIgnoreCase("de") ? "d. MMMM" : "MMMM d", locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------- Config-Pfade
|
||||||
|
|
||||||
|
public boolean isAdvent() { return ADVENT.equals(id); }
|
||||||
|
|
||||||
|
public String titleKey() { return isAdvent() ? "gui.title" : "events." + id + ".title"; }
|
||||||
|
|
||||||
|
public String nameKey(int day) { return rewardLangKey(day) + ".name"; }
|
||||||
|
|
||||||
|
public String loreKey(int day) { return rewardLangKey(day) + ".lore"; }
|
||||||
|
|
||||||
|
private String rewardLangKey(int day) {
|
||||||
|
return (isAdvent() ? "rewards" : "events." + id + ".rewards") + ".day_" + day;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String rewardsPath() { return isAdvent() ? "rewards" : "events." + id + ".rewards"; }
|
||||||
|
|
||||||
|
public boolean isGlobal() {
|
||||||
|
if (globalOverride != null) return globalOverride;
|
||||||
|
return Adventskalender.getInstance().getConfig().getBoolean("calendar.use_global_calendar", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------ Layout
|
||||||
|
|
||||||
|
public int dayForSlot(int slot) {
|
||||||
|
for (int i = 0; i < slots.length; i++) {
|
||||||
|
if (slots[i] == slot) return i + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int slotForDay(int day) { return slots[day - 1]; }
|
||||||
|
|
||||||
|
/** true, wenn die Randspalten links/rechts als Dekoration frei bleiben. */
|
||||||
|
public boolean hasSideBorder() { return perRow == 7; }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------- Getter
|
||||||
|
|
||||||
|
public String getId() { return id; }
|
||||||
|
public boolean isEnabled() { return enabled; }
|
||||||
|
public int getDays() { return days; }
|
||||||
|
public Material getClosedMaterial() { return closedMaterial; }
|
||||||
|
public Material getOpenedMaterial() { return openedMaterial; }
|
||||||
|
public String getClosedTexture() { return closedTexture; }
|
||||||
|
public String getOpenedTexture() { return openedTexture; }
|
||||||
|
public int getTestDay() { return testDay; }
|
||||||
|
public void setTestDay(int day) { this.testDay = day; }
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package de.mviper.adventskalender;
|
|||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@@ -11,17 +12,39 @@ import java.sql.PreparedStatement;
|
|||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle Zugriffe sind synchronisiert, damit sie sicher aus Async-Tasks aufgerufen werden können.
|
||||||
|
* Pro Event werden die Daten getrennt gespeichert. Das Event "advent" nutzt weiterhin die
|
||||||
|
* bisherigen Pfade/Zeilen, damit bestehende Daten erhalten bleiben.
|
||||||
|
*/
|
||||||
public class DataHandler {
|
public class DataHandler {
|
||||||
|
|
||||||
|
public enum ClaimResult { SUCCESS, ALREADY_CLAIMED, ERROR }
|
||||||
|
|
||||||
|
/** Geburtstag eines Spielers ohne Jahr. claimedYear = Jahr des zuletzt abgeholten Geschenks (0 = nie). */
|
||||||
|
public record Birthday(int month, int day, int claimedYear) {}
|
||||||
|
|
||||||
|
/** Wird bei Datenbankfehlern geworfen, damit Aufrufer "nicht vorhanden" und "Fehler" unterscheiden können. */
|
||||||
|
public static class DataException extends RuntimeException {
|
||||||
|
public DataException(String message) { super(message); }
|
||||||
|
}
|
||||||
|
|
||||||
private static File file;
|
private static File file;
|
||||||
private static FileConfiguration dataConfig;
|
private static FileConfiguration dataConfig;
|
||||||
private static boolean mysqlActive = false;
|
private static boolean mysqlActive = false;
|
||||||
|
|
||||||
public static void setup() {
|
public static synchronized void setup() {
|
||||||
|
mysqlActive = false;
|
||||||
|
dataConfig = null;
|
||||||
|
file = null;
|
||||||
|
MySQLManager.close();
|
||||||
|
|
||||||
if (Adventskalender.getInstance().getConfig().getBoolean("mysql.enable")) {
|
if (Adventskalender.getInstance().getConfig().getBoolean("mysql.enable")) {
|
||||||
MySQLManager.connect();
|
MySQLManager.connect();
|
||||||
if (MySQLManager.isConnected()) {
|
if (MySQLManager.isConnected()) {
|
||||||
@@ -43,199 +66,413 @@ public class DataHandler {
|
|||||||
try {
|
try {
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
Adventskalender.getInstance().getLogger().severe("data.yml konnte nicht erstellt werden: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataConfig = YamlConfiguration.loadConfiguration(file);
|
dataConfig = YamlConfiguration.loadConfiguration(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasClaimed(Player player, int day) {
|
/** false, wenn der Datenspeicher gerade nicht benutzbar ist (z.B. MySQL ausgefallen). */
|
||||||
if (isGlobal()) {
|
public static synchronized boolean isReady() {
|
||||||
return GlobalDataManager.isDayClaimedGlobally(day);
|
if (mysqlActive) return MySQLManager.ensureConnected();
|
||||||
|
return dataConfig != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mysqlActive && MySQLManager.isConnected()) {
|
private static void logSql(SQLException e) {
|
||||||
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement("SELECT 1 FROM advent_players WHERE uuid = ? AND day = ?")) {
|
Adventskalender.getInstance().getLogger().severe("Datenbankfehler: " + e.getMessage());
|
||||||
ps.setString(1, player.getUniqueId().toString());
|
}
|
||||||
ps.setInt(2, day);
|
|
||||||
return ps.executeQuery().next();
|
// ------------------------------------------------------------ Pfade / SQL-Helfer
|
||||||
|
|
||||||
|
private static String playersPath(CalendarEvent ev) {
|
||||||
|
return ev.isAdvent() ? "players" : "events." + ev.getId() + ".players";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String globalPath(CalendarEvent ev) {
|
||||||
|
return ev.isAdvent() ? "global" : "events." + ev.getId() + ".global";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ein Event, oder alle konfigurierten Events bei null. */
|
||||||
|
private static List<CalendarEvent> eventsOf(CalendarEvent ev) {
|
||||||
|
return ev != null ? List.of(ev) : EventManager.allConfigured();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int exec(String sql, Object... params) throws SQLException {
|
||||||
|
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement(sql)) {
|
||||||
|
for (int i = 0; i < params.length; i++) ps.setObject(i + 1, params[i]);
|
||||||
|
return ps.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<Integer> queryDays(String sql, Object... params) throws SQLException {
|
||||||
|
Set<Integer> days = new HashSet<>();
|
||||||
|
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement(sql)) {
|
||||||
|
for (int i = 0; i < params.length; i++) ps.setObject(i + 1, params[i]);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
while (rs.next()) days.add(rs.getInt("day"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ Lesen
|
||||||
|
|
||||||
|
/** Tage, die für den Spieler im GUI als geöffnet gelten (global oder pro Spieler). null = Fehler. */
|
||||||
|
public static synchronized Set<Integer> getViewClaimedDays(Player player, CalendarEvent ev) {
|
||||||
|
if (!isReady()) return null;
|
||||||
|
try {
|
||||||
|
return ev.isGlobal() ? loadGlobalDays(ev) : loadPlayerDays(player.getUniqueId().toString(), ev);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
e.printStackTrace();
|
logSql(e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Set<Integer> loadPlayerDays(String uuid, CalendarEvent ev) throws SQLException {
|
||||||
|
if (mysqlActive) {
|
||||||
|
return queryDays("SELECT day FROM advent_players WHERE uuid = ? AND event = ?", uuid, ev.getId());
|
||||||
|
}
|
||||||
|
return new HashSet<>(dataConfig.getIntegerList(playersPath(ev) + "." + uuid + ".claimed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<Integer> loadGlobalDays(CalendarEvent ev) throws SQLException {
|
||||||
|
if (mysqlActive) {
|
||||||
|
return queryDays("SELECT day FROM advent_global WHERE event = ?", ev.getId());
|
||||||
|
}
|
||||||
|
Set<Integer> days = new HashSet<>();
|
||||||
|
ConfigurationSection section = dataConfig.getConfigurationSection(globalPath(ev));
|
||||||
|
if (section != null) {
|
||||||
|
for (String key : section.getKeys(false)) {
|
||||||
|
try {
|
||||||
|
days.add(Integer.parseInt(key.replace("day_", "")));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- Schreiben
|
||||||
|
|
||||||
|
/** Markiert einen Tag atomar als abgeholt. Verhindert Doppelabholung auch bei gleichzeitigen Klicks. */
|
||||||
|
public static synchronized ClaimResult setClaimed(Player player, CalendarEvent ev, int day) {
|
||||||
|
if (!isReady()) return ClaimResult.ERROR;
|
||||||
|
|
||||||
|
if (ev.isGlobal()) {
|
||||||
|
return claimGlobal(ev, day, player.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
String uuid = player.getUniqueId().toString();
|
||||||
|
if (mysqlActive) {
|
||||||
|
try {
|
||||||
|
int rows = exec("INSERT IGNORE INTO advent_players (uuid, event, day, name) VALUES (?, ?, ?, ?)",
|
||||||
|
uuid, ev.getId(), day, player.getName());
|
||||||
|
return rows > 0 ? ClaimResult.SUCCESS : ClaimResult.ALREADY_CLAIMED;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
return ClaimResult.ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String base = playersPath(ev) + "." + uuid;
|
||||||
|
List<Integer> claimed = new ArrayList<>(dataConfig.getIntegerList(base + ".claimed"));
|
||||||
|
if (claimed.contains(day)) return ClaimResult.ALREADY_CLAIMED;
|
||||||
|
claimed.add(day);
|
||||||
|
dataConfig.set(base + ".claimed", claimed);
|
||||||
|
dataConfig.set(base + ".name", player.getName());
|
||||||
|
return save() ? ClaimResult.SUCCESS : ClaimResult.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClaimResult claimGlobal(CalendarEvent ev, int day, String playerName) {
|
||||||
|
if (mysqlActive) {
|
||||||
|
try {
|
||||||
|
int rows = exec("INSERT IGNORE INTO advent_global (event, day, player_name) VALUES (?, ?, ?)",
|
||||||
|
ev.getId(), day, playerName);
|
||||||
|
return rows > 0 ? ClaimResult.SUCCESS : ClaimResult.ALREADY_CLAIMED;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
return ClaimResult.ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String path = globalPath(ev) + ".day_" + day;
|
||||||
|
if (dataConfig.contains(path)) return ClaimResult.ALREADY_CLAIMED;
|
||||||
|
dataConfig.set(path, playerName);
|
||||||
|
return save() ? ClaimResult.SUCCESS : ClaimResult.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Setzt einen Tag für einen Spieler (per Name, auch offline) zurück. ev = null: in allen Events. */
|
||||||
|
public static synchronized boolean resetPlayerDay(String playerName, CalendarEvent ev, int day) {
|
||||||
|
boolean found = false;
|
||||||
|
if (mysqlActive && MySQLManager.ensureConnected()) {
|
||||||
|
String uuid = lookupUuid(playerName);
|
||||||
|
try {
|
||||||
|
if (ev != null) {
|
||||||
|
found |= exec("DELETE FROM advent_players WHERE day = ? AND (name = ? OR uuid = ?) AND event = ?",
|
||||||
|
day, playerName, uuid, ev.getId()) > 0;
|
||||||
|
} else {
|
||||||
|
found |= exec("DELETE FROM advent_players WHERE day = ? AND (name = ? OR uuid = ?)",
|
||||||
|
day, playerName, uuid) > 0;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (dataConfig != null) {
|
if (dataConfig != null) {
|
||||||
List<Integer> claimed = dataConfig.getIntegerList("players." + player.getUniqueId() + ".claimed");
|
for (CalendarEvent e : eventsOf(ev)) {
|
||||||
return claimed.contains(day);
|
ConfigurationSection section = dataConfig.getConfigurationSection(playersPath(e));
|
||||||
|
if (section == null) continue;
|
||||||
|
for (String uuid : section.getKeys(false)) {
|
||||||
|
String base = playersPath(e) + "." + uuid;
|
||||||
|
if (playerName.equalsIgnoreCase(dataConfig.getString(base + ".name"))) {
|
||||||
|
List<Integer> claimed = new ArrayList<>(dataConfig.getIntegerList(base + ".claimed"));
|
||||||
|
found |= claimed.remove(Integer.valueOf(day));
|
||||||
|
dataConfig.set(base + ".claimed", claimed);
|
||||||
|
save();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Löscht alle Daten eines (online) Spielers. ev = null: in allen Events. */
|
||||||
|
public static synchronized void resetPlayer(Player player, CalendarEvent ev) {
|
||||||
|
String uuid = player.getUniqueId().toString();
|
||||||
|
if (mysqlActive && MySQLManager.ensureConnected()) {
|
||||||
|
try {
|
||||||
|
if (ev != null) {
|
||||||
|
exec("DELETE FROM advent_players WHERE uuid = ? AND event = ?", uuid, ev.getId());
|
||||||
|
} else {
|
||||||
|
exec("DELETE FROM advent_players WHERE uuid = ?", uuid);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dataConfig != null) {
|
||||||
|
for (CalendarEvent e : eventsOf(ev)) {
|
||||||
|
dataConfig.set(playersPath(e) + "." + uuid, null);
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Löscht alle Daten eines Spielers per Name (auch offline). ev = null: in allen Events. */
|
||||||
|
public static synchronized boolean resetOfflinePlayer(String name, CalendarEvent ev) {
|
||||||
|
boolean found = false;
|
||||||
|
if (mysqlActive && MySQLManager.ensureConnected()) {
|
||||||
|
String uuid = lookupUuid(name);
|
||||||
|
try {
|
||||||
|
if (ev != null) {
|
||||||
|
found |= exec("DELETE FROM advent_players WHERE (name = ? OR uuid = ?) AND event = ?",
|
||||||
|
name, uuid, ev.getId()) > 0;
|
||||||
|
} else {
|
||||||
|
found |= exec("DELETE FROM advent_players WHERE name = ? OR uuid = ?", name, uuid) > 0;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dataConfig != null) {
|
||||||
|
for (CalendarEvent e : eventsOf(ev)) {
|
||||||
|
ConfigurationSection section = dataConfig.getConfigurationSection(playersPath(e));
|
||||||
|
if (section == null) continue;
|
||||||
|
for (String uuid : section.getKeys(false)) {
|
||||||
|
if (name.equalsIgnoreCase(dataConfig.getString(playersPath(e) + "." + uuid + ".name"))) {
|
||||||
|
dataConfig.set(playersPath(e) + "." + uuid, null);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Löscht alle Spieler- und globalen Daten. ev = null: aller Events. */
|
||||||
|
public static synchronized void resetAll(CalendarEvent ev) {
|
||||||
|
if (mysqlActive && MySQLManager.ensureConnected()) {
|
||||||
|
try {
|
||||||
|
if (ev != null) {
|
||||||
|
exec("DELETE FROM advent_players WHERE event = ?", ev.getId());
|
||||||
|
exec("DELETE FROM advent_global WHERE event = ?", ev.getId());
|
||||||
|
} else {
|
||||||
|
exec("DELETE FROM advent_players");
|
||||||
|
exec("DELETE FROM advent_global");
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dataConfig != null) {
|
||||||
|
for (CalendarEvent e : eventsOf(ev)) {
|
||||||
|
dataConfig.set(playersPath(e), null);
|
||||||
|
dataConfig.set(globalPath(e), null);
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gibt einen global abgeholten Tag wieder frei. ev = null: in allen Events. */
|
||||||
|
public static synchronized void resetGlobalDay(CalendarEvent ev, int day) {
|
||||||
|
if (mysqlActive && MySQLManager.ensureConnected()) {
|
||||||
|
try {
|
||||||
|
if (ev != null) {
|
||||||
|
exec("DELETE FROM advent_global WHERE day = ? AND event = ?", day, ev.getId());
|
||||||
|
} else {
|
||||||
|
exec("DELETE FROM advent_global WHERE day = ?", day);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dataConfig != null) {
|
||||||
|
for (CalendarEvent e : eventsOf(ev)) {
|
||||||
|
dataConfig.set(globalPath(e) + ".day_" + day, null);
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- Geburtstage
|
||||||
|
|
||||||
|
public static synchronized Optional<Birthday> getBirthday(UUID uuid) {
|
||||||
|
if (!isReady()) throw new DataException("Datenspeicher nicht verfügbar");
|
||||||
|
if (mysqlActive) {
|
||||||
|
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement(
|
||||||
|
"SELECT month, day, claimed_year FROM advent_birthdays WHERE uuid = ?")) {
|
||||||
|
ps.setString(1, uuid.toString());
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
if (!rs.next()) return Optional.empty();
|
||||||
|
return Optional.of(new Birthday(rs.getInt("month"), rs.getInt("day"), rs.getInt("claimed_year")));
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
throw new DataException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String base = "birthdays." + uuid;
|
||||||
|
if (!dataConfig.contains(base + ".month")) return Optional.empty();
|
||||||
|
return Optional.of(new Birthday(dataConfig.getInt(base + ".month"), dataConfig.getInt(base + ".day"),
|
||||||
|
dataConfig.getInt(base + ".claimed_year", 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Speichert den Geburtstag. onlyIfAbsent: nichts überschreiben. Gibt zurück, ob geschrieben wurde. */
|
||||||
|
public static synchronized boolean setBirthday(UUID uuid, String name, int month, int day, boolean onlyIfAbsent) {
|
||||||
|
if (!isReady()) throw new DataException("Datenspeicher nicht verfügbar");
|
||||||
|
if (mysqlActive) {
|
||||||
|
try {
|
||||||
|
if (onlyIfAbsent) {
|
||||||
|
return exec("INSERT IGNORE INTO advent_birthdays (uuid, name, month, day) VALUES (?, ?, ?, ?)",
|
||||||
|
uuid.toString(), name, month, day) > 0;
|
||||||
|
}
|
||||||
|
exec("INSERT INTO advent_birthdays (uuid, name, month, day) VALUES (?, ?, ?, ?) "
|
||||||
|
+ "ON DUPLICATE KEY UPDATE name = VALUES(name), month = VALUES(month), day = VALUES(day)",
|
||||||
|
uuid.toString(), name, month, day);
|
||||||
|
return true;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
throw new DataException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String base = "birthdays." + uuid;
|
||||||
|
if (onlyIfAbsent && dataConfig.contains(base + ".month")) return false;
|
||||||
|
dataConfig.set(base + ".name", name);
|
||||||
|
dataConfig.set(base + ".month", month);
|
||||||
|
dataConfig.set(base + ".day", day);
|
||||||
|
if (!save()) throw new DataException("data.yml konnte nicht gespeichert werden");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Löscht einen Geburtstag per UUID oder Name (einer von beiden darf null sein). */
|
||||||
|
public static synchronized boolean removeBirthday(UUID uuid, String name) {
|
||||||
|
if (!isReady()) throw new DataException("Datenspeicher nicht verfügbar");
|
||||||
|
boolean found = false;
|
||||||
|
if (mysqlActive) {
|
||||||
|
try {
|
||||||
|
found = exec("DELETE FROM advent_birthdays WHERE uuid = ? OR name = ?",
|
||||||
|
uuid != null ? uuid.toString() : "", name != null ? name : "") > 0;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
throw new DataException(e.getMessage());
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
ConfigurationSection section = dataConfig.getConfigurationSection("birthdays");
|
||||||
|
if (section == null) return false;
|
||||||
|
for (String key : section.getKeys(false)) {
|
||||||
|
boolean match = (uuid != null && key.equals(uuid.toString()))
|
||||||
|
|| (name != null && name.equalsIgnoreCase(dataConfig.getString("birthdays." + key + ".name")));
|
||||||
|
if (match) {
|
||||||
|
dataConfig.set("birthdays." + key, null);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) save();
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Markiert das Geschenk für das Geburtstagsjahr atomar als abgeholt. */
|
||||||
|
public static synchronized ClaimResult claimBirthday(UUID uuid, int year) {
|
||||||
|
if (!isReady()) return ClaimResult.ERROR;
|
||||||
|
if (mysqlActive) {
|
||||||
|
try {
|
||||||
|
int rows = exec("UPDATE advent_birthdays SET claimed_year = ? WHERE uuid = ? AND claimed_year < ?",
|
||||||
|
year, uuid.toString(), year);
|
||||||
|
return rows > 0 ? ClaimResult.SUCCESS : ClaimResult.ALREADY_CLAIMED;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
return ClaimResult.ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String path = "birthdays." + uuid + ".claimed_year";
|
||||||
|
if (dataConfig.getInt(path, 0) >= year) return ClaimResult.ALREADY_CLAIMED;
|
||||||
|
dataConfig.set(path, year);
|
||||||
|
return save() ? ClaimResult.SUCCESS : ClaimResult.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Macht die Abholung rückgängig (Admin-Reset bzw. wenn der Spieler vor der Vergabe offline ging). */
|
||||||
|
public static synchronized boolean resetBirthdayClaim(UUID uuid, String name) {
|
||||||
|
if (!isReady()) return false;
|
||||||
|
if (mysqlActive) {
|
||||||
|
try {
|
||||||
|
return exec("UPDATE advent_birthdays SET claimed_year = 0 WHERE uuid = ? OR name = ?",
|
||||||
|
uuid != null ? uuid.toString() : "", name != null ? name : "") > 0;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logSql(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConfigurationSection section = dataConfig.getConfigurationSection("birthdays");
|
||||||
|
if (section == null) return false;
|
||||||
|
for (String key : section.getKeys(false)) {
|
||||||
|
boolean match = (uuid != null && key.equals(uuid.toString()))
|
||||||
|
|| (name != null && name.equalsIgnoreCase(dataConfig.getString("birthdays." + key + ".name")));
|
||||||
|
if (match) {
|
||||||
|
dataConfig.set("birthdays." + key + ".claimed_year", 0);
|
||||||
|
return save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setClaimed(Player player, int day) {
|
|
||||||
if (isGlobal()) {
|
|
||||||
GlobalDataManager.setDayClaimedGlobally(day, player.getName());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mysqlActive && MySQLManager.isConnected()) {
|
|
||||||
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement("INSERT IGNORE INTO advent_players (uuid, day) VALUES (?, ?)")) {
|
|
||||||
ps.setString(1, player.getUniqueId().toString());
|
|
||||||
ps.setInt(2, day);
|
|
||||||
ps.executeUpdate();
|
|
||||||
return;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataConfig != null) {
|
|
||||||
List<Integer> claimed = dataConfig.getIntegerList("players." + player.getUniqueId() + ".claimed");
|
|
||||||
if (!claimed.contains(day)) {
|
|
||||||
claimed.add(day);
|
|
||||||
dataConfig.set("players." + player.getUniqueId() + ".claimed", claimed);
|
|
||||||
dataConfig.set("players." + player.getUniqueId() + ".name", player.getName());
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void resetPlayerDay(String playerName, int day) {
|
|
||||||
if (mysqlActive && MySQLManager.isConnected()) {
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
OfflinePlayer target = Bukkit.getOfflinePlayer(playerName);
|
private static String lookupUuid(String name) {
|
||||||
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement("DELETE FROM advent_players WHERE uuid = ? AND day = ?")) {
|
OfflinePlayer target = Bukkit.getOfflinePlayer(name);
|
||||||
ps.setString(1, target.getUniqueId().toString());
|
return target.getUniqueId().toString();
|
||||||
ps.setInt(2, day);
|
|
||||||
ps.executeUpdate();
|
|
||||||
} catch (SQLException e) { e.printStackTrace(); }
|
|
||||||
}
|
|
||||||
if (dataConfig != null && dataConfig.getConfigurationSection("players") != null) {
|
|
||||||
for (String uuid : dataConfig.getConfigurationSection("players").getKeys(false)) {
|
|
||||||
if (playerName.equalsIgnoreCase(dataConfig.getString("players." + uuid + ".name"))) {
|
|
||||||
List<Integer> claimed = dataConfig.getIntegerList("players." + uuid + ".claimed");
|
|
||||||
claimed.remove(Integer.valueOf(day));
|
|
||||||
dataConfig.set("players." + uuid + ".claimed", claimed);
|
|
||||||
save();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Set<Integer> getClaimedDays(Player player) {
|
private static boolean save() {
|
||||||
if (mysqlActive && MySQLManager.isConnected()) {
|
if (dataConfig == null || file == null) return false;
|
||||||
Set<Integer> days = new HashSet<>();
|
|
||||||
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement("SELECT day FROM advent_players WHERE uuid = ?")) {
|
|
||||||
ps.setString(1, player.getUniqueId().toString());
|
|
||||||
ResultSet rs = ps.executeQuery();
|
|
||||||
while (rs.next()) days.add(rs.getInt("day"));
|
|
||||||
return days;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataConfig != null) {
|
|
||||||
return new HashSet<>(dataConfig.getIntegerList("players." + player.getUniqueId() + ".claimed"));
|
|
||||||
}
|
|
||||||
return new HashSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void resetPlayer(Player player) {
|
|
||||||
if (mysqlActive && MySQLManager.isConnected()) {
|
|
||||||
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement("DELETE FROM advent_players WHERE uuid = ?")) {
|
|
||||||
ps.setString(1, player.getUniqueId().toString());
|
|
||||||
ps.executeUpdate();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dataConfig != null) {
|
|
||||||
dataConfig.set("players." + player.getUniqueId(), null);
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void resetOfflinePlayer(String name) {
|
|
||||||
if (dataConfig == null || dataConfig.getConfigurationSection("players") == null) return;
|
|
||||||
for (String uuid : dataConfig.getConfigurationSection("players").getKeys(false)) {
|
|
||||||
String savedName = dataConfig.getString("players." + uuid + ".name");
|
|
||||||
if (name.equalsIgnoreCase(savedName)) {
|
|
||||||
dataConfig.set("players." + uuid, null);
|
|
||||||
save();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void resetAll() {
|
|
||||||
if (mysqlActive && MySQLManager.isConnected()) {
|
|
||||||
try (PreparedStatement ps1 = MySQLManager.getConnection().prepareStatement("DELETE FROM advent_players");
|
|
||||||
PreparedStatement ps2 = MySQLManager.getConnection().prepareStatement("DELETE FROM advent_global")) {
|
|
||||||
ps1.executeUpdate();
|
|
||||||
ps2.executeUpdate();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dataConfig != null) {
|
|
||||||
dataConfig.set("players", null);
|
|
||||||
dataConfig.set("global", null);
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isGlobal() {
|
|
||||||
return Adventskalender.getInstance().getConfig().getBoolean("calendar.use_global_calendar", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void save() {
|
|
||||||
if (dataConfig == null || file == null) return;
|
|
||||||
try {
|
try {
|
||||||
dataConfig.save(file);
|
dataConfig.save(file);
|
||||||
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
Adventskalender.getInstance().getLogger().severe("data.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||||
}
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
public static class GlobalDataManager {
|
|
||||||
public static boolean isDayClaimedGlobally(int day) {
|
|
||||||
if (mysqlActive && MySQLManager.isConnected()) {
|
|
||||||
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement("SELECT 1 FROM advent_global WHERE day = ?")) {
|
|
||||||
ps.setInt(1, day);
|
|
||||||
return ps.executeQuery().next();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dataConfig != null && dataConfig.contains("global.day_" + day);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setDayClaimedGlobally(int day, String playerName) {
|
|
||||||
if (mysqlActive && MySQLManager.isConnected()) {
|
|
||||||
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement("REPLACE INTO advent_global (day, player_name) VALUES (?, ?)")) {
|
|
||||||
ps.setInt(1, day);
|
|
||||||
ps.setString(2, playerName);
|
|
||||||
ps.executeUpdate();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} else if (dataConfig != null) {
|
|
||||||
dataConfig.set("global.day_" + day, playerName);
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void resetGlobalDay(int day) {
|
|
||||||
if (mysqlActive && MySQLManager.isConnected()) {
|
|
||||||
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement("DELETE FROM advent_global WHERE day = ?")) {
|
|
||||||
ps.setInt(1, day);
|
|
||||||
ps.executeUpdate();
|
|
||||||
} catch (SQLException e) { e.printStackTrace(); }
|
|
||||||
} else if (dataConfig != null) {
|
|
||||||
dataConfig.set("global.day_" + day, null);
|
|
||||||
DataHandler.save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package de.mviper.adventskalender;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/** Verwaltet alle konfigurierten Kalender-Events. */
|
||||||
|
public final class EventManager {
|
||||||
|
|
||||||
|
private static volatile Map<String, CalendarEvent> events = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private EventManager() {}
|
||||||
|
|
||||||
|
public static void load() {
|
||||||
|
Adventskalender plugin = Adventskalender.getInstance();
|
||||||
|
FileConfiguration cfg = plugin.getConfig();
|
||||||
|
int startTestDay = Math.max(0, cfg.getInt("general.test_day", 0));
|
||||||
|
|
||||||
|
// IDs aus der Config des Servers und den mitgelieferten Standardwerten (Standard zuerst)
|
||||||
|
Set<String> ids = new LinkedHashSet<>();
|
||||||
|
if (cfg.getDefaults() != null) {
|
||||||
|
ConfigurationSection def = cfg.getDefaults().getConfigurationSection("events");
|
||||||
|
if (def != null) ids.addAll(def.getKeys(false));
|
||||||
|
}
|
||||||
|
ConfigurationSection user = cfg.getConfigurationSection("events");
|
||||||
|
if (user != null) ids.addAll(user.getKeys(false));
|
||||||
|
if (ids.isEmpty()) ids.add(CalendarEvent.ADVENT);
|
||||||
|
|
||||||
|
Map<String, CalendarEvent> loaded = new LinkedHashMap<>();
|
||||||
|
for (String id : ids) {
|
||||||
|
try {
|
||||||
|
CalendarEvent ev = new CalendarEvent(id, cfg, startTestDay);
|
||||||
|
loaded.put(ev.getId(), ev);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
plugin.getLogger().warning("Event '" + id + "' wird übersprungen: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events = loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Alle aktivierten Events. */
|
||||||
|
public static List<CalendarEvent> all() {
|
||||||
|
List<CalendarEvent> list = new ArrayList<>();
|
||||||
|
for (CalendarEvent ev : events.values()) {
|
||||||
|
if (ev.isEnabled()) list.add(ev);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Alle Events, auch deaktivierte (z.B. für Admin-Resets). */
|
||||||
|
public static List<CalendarEvent> allConfigured() {
|
||||||
|
return Collections.unmodifiableList(new ArrayList<>(events.values()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Aktiviertes Event per ID, sonst null. */
|
||||||
|
public static CalendarEvent get(String id) {
|
||||||
|
CalendarEvent ev = id == null ? null : events.get(id.toLowerCase());
|
||||||
|
return (ev != null && ev.isEnabled()) ? ev : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Event per ID, auch wenn es deaktiviert ist. */
|
||||||
|
public static CalendarEvent getAny(String id) {
|
||||||
|
return id == null ? null : events.get(id.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Das Event, das gerade läuft; sonst das erste aktivierte. null, wenn keines aktiviert ist. */
|
||||||
|
public static CalendarEvent defaultEvent() {
|
||||||
|
CalendarEvent first = null;
|
||||||
|
for (CalendarEvent ev : all()) {
|
||||||
|
if (first == null) first = ev;
|
||||||
|
if (ev.currentDay() > 0) return ev;
|
||||||
|
}
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,10 @@ package de.mviper.adventskalender;
|
|||||||
import org.bukkit.ChatColor;
|
import org.bukkit.ChatColor;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -11,10 +15,28 @@ public class LanguageManager {
|
|||||||
private static String prefix;
|
private static String prefix;
|
||||||
|
|
||||||
public static void setup() {
|
public static void setup() {
|
||||||
String lang = Adventskalender.getInstance().getConfig().getString("general.language", "de");
|
Adventskalender plugin = Adventskalender.getInstance();
|
||||||
File f = new File(Adventskalender.getInstance().getDataFolder(), "messages_" + lang + ".yml");
|
String lang = plugin.getConfig().getString("general.language", "de").toLowerCase();
|
||||||
if (!f.exists()) Adventskalender.getInstance().saveResource("messages_" + lang + ".yml", false);
|
if (!lang.matches("[a-z_]+")) lang = "de";
|
||||||
|
|
||||||
|
String resName = "messages_" + lang + ".yml";
|
||||||
|
File f = new File(plugin.getDataFolder(), resName);
|
||||||
|
if (!f.exists() && plugin.getResource(resName) == null) {
|
||||||
|
plugin.getLogger().warning("Sprachdatei '" + resName + "' existiert nicht - nutze 'de'.");
|
||||||
|
resName = "messages_de.yml";
|
||||||
|
f = new File(plugin.getDataFolder(), resName);
|
||||||
|
}
|
||||||
|
if (!f.exists()) plugin.saveResource(resName, false);
|
||||||
cfg = YamlConfiguration.loadConfiguration(f);
|
cfg = YamlConfiguration.loadConfiguration(f);
|
||||||
|
|
||||||
|
// Fehlende Schlüssel (z.B. nach einem Update) aus der mitgelieferten Datei ergänzen
|
||||||
|
try (InputStream in = plugin.getResource(resName)) {
|
||||||
|
if (in != null) {
|
||||||
|
cfg.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(in, StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
prefix = ChatColor.translateAlternateColorCodes('&', cfg.getString("prefix", "&6[Advent] "));
|
prefix = ChatColor.translateAlternateColorCodes('&', cfg.getString("prefix", "&6[Advent] "));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,10 +45,29 @@ public class LanguageManager {
|
|||||||
return s == null ? "§cKey error: " + path : ChatColor.translateAlternateColorCodes('&', s);
|
return s == null ? "§cKey error: " + path : ChatColor.translateAlternateColorCodes('&', s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean has(String path) {
|
||||||
|
return cfg.contains(path);
|
||||||
|
}
|
||||||
|
|
||||||
public static String getMessage(String path) {
|
public static String getMessage(String path) {
|
||||||
return prefix + getString(path);
|
return prefix + getString(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Nachricht mit dem Präfix aus prefixKey (z.B. "birthday.prefix"); fehlt er, gilt das Standard-Präfix. */
|
||||||
|
public static String getMessage(String path, String prefixKey) {
|
||||||
|
return prefixOf(prefixKey) + getString(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nachricht mit dem Präfix des Events (z.B. [Halloween]); der Advent nutzt das Standard-Präfix. */
|
||||||
|
public static String getMessage(String path, CalendarEvent event) {
|
||||||
|
return getMessage(path, event == null || event.isAdvent() ? null : "events." + event.getId() + ".prefix");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String prefixOf(String prefixKey) {
|
||||||
|
if (prefixKey == null || !cfg.contains(prefixKey)) return prefix;
|
||||||
|
return ChatColor.translateAlternateColorCodes('&', cfg.getString(prefixKey, ""));
|
||||||
|
}
|
||||||
|
|
||||||
public static List<String> getStringList(String path) {
|
public static List<String> getStringList(String path) {
|
||||||
return cfg.getStringList(path).stream()
|
return cfg.getStringList(path).stream()
|
||||||
.map(s -> ChatColor.translateAlternateColorCodes('&', s))
|
.map(s -> ChatColor.translateAlternateColorCodes('&', s))
|
||||||
|
|||||||
@@ -2,15 +2,13 @@ package de.mviper.adventskalender;
|
|||||||
|
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class MySQLManager {
|
public class MySQLManager {
|
||||||
|
|
||||||
private static Connection connection;
|
private static Connection connection;
|
||||||
|
|
||||||
public static void connect() {
|
/** Baut die Verbindung (neu) auf. Eine bestehende Verbindung wird vorher geschlossen. */
|
||||||
|
public static synchronized void connect() {
|
||||||
FileConfiguration config = Adventskalender.getInstance().getConfig();
|
FileConfiguration config = Adventskalender.getInstance().getConfig();
|
||||||
String host = config.getString("mysql.host");
|
String host = config.getString("mysql.host");
|
||||||
int port = config.getInt("mysql.port");
|
int port = config.getInt("mysql.port");
|
||||||
@@ -18,33 +16,62 @@ public class MySQLManager {
|
|||||||
String user = config.getString("mysql.user");
|
String user = config.getString("mysql.user");
|
||||||
String password = config.getString("mysql.password");
|
String password = config.getString("mysql.password");
|
||||||
|
|
||||||
|
close();
|
||||||
try {
|
try {
|
||||||
if (connection != null && !connection.isClosed()) return;
|
loadDriver();
|
||||||
|
connection = DriverManager.getConnection(
|
||||||
synchronized (MySQLManager.class) {
|
"jdbc:mysql://" + host + ":" + port + "/" + database + "?useSSL=false&characterEncoding=utf8", user, password);
|
||||||
Class.forName("com.mysql.jdbc.Driver");
|
|
||||||
connection = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database + "?autoReconnect=true&useSSL=false", user, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
createTables();
|
createTables();
|
||||||
Adventskalender.getInstance().getLogger().info("MySQL-Verbindung erfolgreich hergestellt!");
|
Adventskalender.getInstance().getLogger().info("MySQL-Verbindung erfolgreich hergestellt!");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
connection = null;
|
||||||
Adventskalender.getInstance().getLogger().severe("MySQL-Verbindung fehlgeschlagen: " + e.getMessage());
|
Adventskalender.getInstance().getLogger().severe("MySQL-Verbindung fehlgeschlagen: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void loadDriver() throws ClassNotFoundException {
|
||||||
|
try {
|
||||||
|
Class.forName("com.mysql.cj.jdbc.Driver");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
Class.forName("com.mysql.jdbc.Driver");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void createTables() {
|
private static void createTables() {
|
||||||
try (Statement s = connection.createStatement()) {
|
try (Statement s = connection.createStatement()) {
|
||||||
// Tabelle für Einzelspieler-Daten
|
// Tabelle für Einzelspieler-Daten
|
||||||
s.executeUpdate("CREATE TABLE IF NOT EXISTS advent_players (uuid VARCHAR(36), day INT, PRIMARY KEY(uuid, day))");
|
s.executeUpdate("CREATE TABLE IF NOT EXISTS advent_players (uuid VARCHAR(36), event VARCHAR(32) NOT NULL DEFAULT 'advent', day INT, name VARCHAR(32), PRIMARY KEY(uuid, event, day))");
|
||||||
// Tabelle für globalen Modus
|
// Tabelle für globalen Modus
|
||||||
s.executeUpdate("CREATE TABLE IF NOT EXISTS advent_global (day INT PRIMARY KEY, player_name VARCHAR(16))");
|
s.executeUpdate("CREATE TABLE IF NOT EXISTS advent_global (event VARCHAR(32) NOT NULL DEFAULT 'advent', day INT, player_name VARCHAR(32), PRIMARY KEY(event, day))");
|
||||||
|
// Tabelle für Spieler-Geburtstage (ohne Jahr)
|
||||||
|
s.executeUpdate("CREATE TABLE IF NOT EXISTS advent_birthdays (uuid VARCHAR(36) PRIMARY KEY, name VARCHAR(32), month INT, day INT, claimed_year INT NOT NULL DEFAULT 0)");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
e.printStackTrace();
|
Adventskalender.getInstance().getLogger().severe("MySQL-Tabellen konnten nicht erstellt werden: " + e.getMessage());
|
||||||
|
}
|
||||||
|
// Migrationen für Tabellen aus älteren Versionen (Fehler = Spalte existiert schon)
|
||||||
|
migrate("ALTER TABLE advent_players ADD COLUMN name VARCHAR(32)", null);
|
||||||
|
migrate("ALTER TABLE advent_players ADD COLUMN event VARCHAR(32) NOT NULL DEFAULT 'advent'",
|
||||||
|
"ALTER TABLE advent_players DROP PRIMARY KEY, ADD PRIMARY KEY (uuid, event, day)");
|
||||||
|
migrate("ALTER TABLE advent_global ADD COLUMN event VARCHAR(32) NOT NULL DEFAULT 'advent'",
|
||||||
|
"ALTER TABLE advent_global DROP PRIMARY KEY, ADD PRIMARY KEY (event, day)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Führt eine Migration aus; followUp läuft nur, wenn sie erfolgreich war. */
|
||||||
|
private static void migrate(String sql, String followUp) {
|
||||||
|
try (Statement s = connection.createStatement()) {
|
||||||
|
s.executeUpdate(sql);
|
||||||
|
} catch (SQLException ignored) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (followUp == null) return;
|
||||||
|
try (Statement s = connection.createStatement()) {
|
||||||
|
s.executeUpdate(followUp);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
Adventskalender.getInstance().getLogger().severe("MySQL-Migration fehlgeschlagen: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isConnected() {
|
public static synchronized boolean isConnected() {
|
||||||
try {
|
try {
|
||||||
return connection != null && !connection.isClosed();
|
return connection != null && !connection.isClosed();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
@@ -52,15 +79,26 @@ public class MySQLManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void close() {
|
/** Prüft die Verbindung und baut sie bei Bedarf neu auf. */
|
||||||
|
public static synchronized boolean ensureConnected() {
|
||||||
try {
|
try {
|
||||||
if (isConnected()) connection.close();
|
if (connection != null && connection.isValid(2)) return true;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException ignored) {
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
connect();
|
||||||
|
return connection != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Connection getConnection() {
|
public static synchronized void close() {
|
||||||
|
try {
|
||||||
|
if (connection != null) connection.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
Adventskalender.getInstance().getLogger().warning("MySQL-Verbindung konnte nicht sauber geschlossen werden: " + e.getMessage());
|
||||||
|
}
|
||||||
|
connection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized Connection getConnection() {
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package de.mviper.adventskalender;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.enchantments.Enchantment;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** Gemeinsame Logik zum Bauen und Übergeben von Belohnungs-Items aus der Config. */
|
||||||
|
public final class RewardUtil {
|
||||||
|
|
||||||
|
private RewardUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut ein Item aus dem Config-Abschnitt (material, amount, enchantments).
|
||||||
|
* Gibt null zurück, wenn das Material ungültig ist.
|
||||||
|
*/
|
||||||
|
public static ItemStack build(String path, String label) {
|
||||||
|
Adventskalender plugin = Adventskalender.getInstance();
|
||||||
|
String matName = plugin.getConfig().getString(path + ".material", "COOKIE");
|
||||||
|
int amount = Math.max(1, plugin.getConfig().getInt(path + ".amount", 1));
|
||||||
|
|
||||||
|
Material material = Material.matchMaterial(matName);
|
||||||
|
if (material == null || !material.isItem() || material.isAir()) {
|
||||||
|
plugin.getLogger().warning("Ungültiges Material '" + matName + "' für " + label + " in der config.yml!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack reward = new ItemStack(material, amount);
|
||||||
|
ItemMeta meta = reward.getItemMeta();
|
||||||
|
|
||||||
|
if (meta != null) {
|
||||||
|
List<Map<?, ?>> enchants = plugin.getConfig().getMapList(path + ".enchantments");
|
||||||
|
for (Map<?, ?> entry : enchants) {
|
||||||
|
Object typeObj = entry.get("type");
|
||||||
|
if (typeObj == null) continue;
|
||||||
|
String type = String.valueOf(typeObj);
|
||||||
|
Object levelObj = entry.get("level");
|
||||||
|
int level = (levelObj instanceof Number n) ? n.intValue() : 1;
|
||||||
|
|
||||||
|
Enchantment enc = findEnchantment(type);
|
||||||
|
if (enc != null) {
|
||||||
|
meta.addEnchant(enc, level, true);
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("Unbekannte Verzauberung '" + type + "' für " + label + " in der config.yml!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reward.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return reward;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sucht eine Verzauberung über ihren Minecraft-Key (z.B. SHARPNESS), mit Fallback auf alte Bukkit-Namen. */
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static Enchantment findEnchantment(String type) {
|
||||||
|
String name = type.trim();
|
||||||
|
NamespacedKey key = NamespacedKey.fromString(name.toLowerCase());
|
||||||
|
Enchantment enc = key != null ? Enchantment.getByKey(key) : null;
|
||||||
|
return enc != null ? enc : Enchantment.getByName(name.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Legt das Item ins Inventar, Überschuss wird gedroppt. Gibt true zurück, wenn etwas gedroppt wurde. */
|
||||||
|
public static boolean give(Player player, ItemStack reward) {
|
||||||
|
Map<Integer, ItemStack> leftover = player.getInventory().addItem(reward);
|
||||||
|
for (ItemStack item : leftover.values()) {
|
||||||
|
player.getWorld().dropItemNaturally(player.getLocation(), item);
|
||||||
|
}
|
||||||
|
return !leftover.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@ package de.mviper.adventskalender;
|
|||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URI;
|
||||||
|
import java.net.URLConnection;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -20,12 +20,16 @@ public class UpdateChecker {
|
|||||||
|
|
||||||
public void getVersion(final Consumer<String> consumer) {
|
public void getVersion(final Consumer<String> consumer) {
|
||||||
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();
|
try {
|
||||||
Scanner scann = new Scanner(is)) {
|
URLConnection connection = URI.create("https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId).toURL().openConnection();
|
||||||
|
connection.setConnectTimeout(5000);
|
||||||
|
connection.setReadTimeout(5000);
|
||||||
|
try (InputStream is = connection.getInputStream(); Scanner scann = new Scanner(is)) {
|
||||||
if (scann.hasNext()) {
|
if (scann.hasNext()) {
|
||||||
consumer.accept(scann.next());
|
consumer.accept(scann.next());
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
plugin.getLogger().info("Update-Check fehlgeschlagen: " + e.getMessage());
|
plugin.getLogger().info("Update-Check fehlgeschlagen: " + e.getMessage());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ general:
|
|||||||
# Verfügbare Optionen: 'de', 'en'
|
# Verfügbare Optionen: 'de', 'en'
|
||||||
language: "de"
|
language: "de"
|
||||||
|
|
||||||
# Nur für Testzwecke: Simuliert einen Tag (0 = echtes Datum verwenden)
|
# Zeitzone für das Datum, z.B. "Europe/Berlin". Leer = Zeitzone des Servers.
|
||||||
|
timezone: ""
|
||||||
|
|
||||||
|
# Nur für Testzwecke: Simuliert einen Tag beim Start (0 = echtes Datum verwenden)
|
||||||
|
# Mit /ak admin test <Tag> kann der Tag zur Laufzeit geändert werden (wird nicht gespeichert).
|
||||||
test_day: 0
|
test_day: 0
|
||||||
|
|
||||||
# --- Kalender-Einstellungen ---
|
# --- Kalender-Einstellungen ---
|
||||||
@@ -209,3 +213,274 @@ rewards:
|
|||||||
amount: 64
|
amount: 64
|
||||||
key: "rewards.day_24"
|
key: "rewards.day_24"
|
||||||
enchantments: []
|
enchantments: []
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Events (Kalender)
|
||||||
|
# =============================================================================
|
||||||
|
# Jedes Event hat einen eigenen Kalender, eigene Belohnungen und eigene Daten.
|
||||||
|
# Öffnen mit /ak (laufendes Event) oder /ak <event>.
|
||||||
|
#
|
||||||
|
# enabled: Event an/aus
|
||||||
|
# days: Anzahl der Türchen (1-36)
|
||||||
|
# start: Startdatum im Format MM-dd (z.B. "10-01") ODER relativ zu Ostersonntag
|
||||||
|
# (z.B. "easter-13" = 13 Tage vor Ostersonntag, "easter+1" = Ostermontag).
|
||||||
|
# Das Datum wird jedes Jahr automatisch berechnet.
|
||||||
|
# grace_days: Wie viele Tage nach dem letzten Türchen der Kalender noch geöffnet bleibt.
|
||||||
|
# global: (optional) true/false überschreibt calendar.use_global_calendar nur für dieses Event.
|
||||||
|
# closed_material / opened_material: Items für geschlossene bzw. geöffnete Türchen.
|
||||||
|
# Die Stackgröße zeigt den Tag an. (Das Advent-Event nutzt eigene Köpfe.)
|
||||||
|
# closed_texture / opened_texture: (optional) Textur-URL eines Kopfes statt der Materialien.
|
||||||
|
#
|
||||||
|
# Die Texte (Titel, Namen, Lore) stehen in der Sprachdatei unter events.<id>.
|
||||||
|
# Die Belohnungen des Advent-Events stehen weiterhin oben unter "rewards".
|
||||||
|
events:
|
||||||
|
advent:
|
||||||
|
enabled: true
|
||||||
|
days: 24
|
||||||
|
start: "12-01"
|
||||||
|
grace_days: 7
|
||||||
|
|
||||||
|
halloween:
|
||||||
|
enabled: true
|
||||||
|
days: 31
|
||||||
|
start: "10-01"
|
||||||
|
grace_days: 0
|
||||||
|
closed_material: PUMPKIN
|
||||||
|
opened_material: JACK_O_LANTERN
|
||||||
|
rewards:
|
||||||
|
1:
|
||||||
|
material: PUMPKIN_PIE
|
||||||
|
amount: 8
|
||||||
|
enchantments: []
|
||||||
|
2:
|
||||||
|
material: JACK_O_LANTERN
|
||||||
|
amount: 4
|
||||||
|
enchantments: []
|
||||||
|
3:
|
||||||
|
material: BONE
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
4:
|
||||||
|
material: SPIDER_EYE
|
||||||
|
amount: 8
|
||||||
|
enchantments: []
|
||||||
|
5:
|
||||||
|
material: COBWEB
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
6:
|
||||||
|
material: BLACK_CANDLE
|
||||||
|
amount: 4
|
||||||
|
enchantments: []
|
||||||
|
7:
|
||||||
|
material: SOUL_TORCH
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
8:
|
||||||
|
material: FERMENTED_SPIDER_EYE
|
||||||
|
amount: 4
|
||||||
|
enchantments: []
|
||||||
|
9:
|
||||||
|
material: GHAST_TEAR
|
||||||
|
amount: 2
|
||||||
|
enchantments: []
|
||||||
|
10:
|
||||||
|
material: SOUL_LANTERN
|
||||||
|
amount: 4
|
||||||
|
enchantments: []
|
||||||
|
11:
|
||||||
|
material: PHANTOM_MEMBRANE
|
||||||
|
amount: 4
|
||||||
|
enchantments: []
|
||||||
|
12:
|
||||||
|
material: REDSTONE
|
||||||
|
amount: 32
|
||||||
|
enchantments: []
|
||||||
|
13:
|
||||||
|
material: GLOWSTONE_DUST
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
14:
|
||||||
|
material: BLAZE_ROD
|
||||||
|
amount: 4
|
||||||
|
enchantments: []
|
||||||
|
15:
|
||||||
|
material: ENDER_PEARL
|
||||||
|
amount: 4
|
||||||
|
enchantments: []
|
||||||
|
16:
|
||||||
|
material: SKELETON_SKULL
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
17:
|
||||||
|
material: ZOMBIE_HEAD
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
18:
|
||||||
|
material: CREEPER_HEAD
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
19:
|
||||||
|
material: GOLDEN_APPLE
|
||||||
|
amount: 3
|
||||||
|
enchantments: []
|
||||||
|
20:
|
||||||
|
material: DIAMOND_SWORD
|
||||||
|
amount: 1
|
||||||
|
enchantments:
|
||||||
|
- type: SMITE
|
||||||
|
level: 5
|
||||||
|
- type: UNBREAKING
|
||||||
|
level: 3
|
||||||
|
21:
|
||||||
|
material: BOW
|
||||||
|
amount: 1
|
||||||
|
enchantments:
|
||||||
|
- type: POWER
|
||||||
|
level: 4
|
||||||
|
- type: INFINITY
|
||||||
|
level: 1
|
||||||
|
22:
|
||||||
|
material: NETHERITE_SCRAP
|
||||||
|
amount: 4
|
||||||
|
enchantments: []
|
||||||
|
23:
|
||||||
|
material: WITHER_ROSE
|
||||||
|
amount: 2
|
||||||
|
enchantments: []
|
||||||
|
24:
|
||||||
|
material: EXPERIENCE_BOTTLE
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
25:
|
||||||
|
material: TOTEM_OF_UNDYING
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
26:
|
||||||
|
material: NAME_TAG
|
||||||
|
amount: 2
|
||||||
|
enchantments: []
|
||||||
|
27:
|
||||||
|
material: MUSIC_DISC_13
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
28:
|
||||||
|
material: DIAMOND
|
||||||
|
amount: 8
|
||||||
|
enchantments: []
|
||||||
|
29:
|
||||||
|
material: WITHER_SKELETON_SKULL
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
30:
|
||||||
|
material: NETHER_STAR
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
31:
|
||||||
|
material: NETHERITE_INGOT
|
||||||
|
amount: 3
|
||||||
|
enchantments: []
|
||||||
|
|
||||||
|
easter:
|
||||||
|
enabled: true
|
||||||
|
days: 14
|
||||||
|
start: "easter-13"
|
||||||
|
grace_days: 7
|
||||||
|
closed_material: EGG
|
||||||
|
opened_material: TURTLE_EGG
|
||||||
|
rewards:
|
||||||
|
1:
|
||||||
|
material: EGG
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
2:
|
||||||
|
material: CARROT
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
3:
|
||||||
|
material: GOLDEN_CARROT
|
||||||
|
amount: 8
|
||||||
|
enchantments: []
|
||||||
|
4:
|
||||||
|
material: RABBIT_FOOT
|
||||||
|
amount: 2
|
||||||
|
enchantments: []
|
||||||
|
5:
|
||||||
|
material: COOKIE
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
6:
|
||||||
|
material: HONEY_BOTTLE
|
||||||
|
amount: 4
|
||||||
|
enchantments: []
|
||||||
|
7:
|
||||||
|
material: CAKE
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
8:
|
||||||
|
material: PINK_TULIP
|
||||||
|
amount: 8
|
||||||
|
enchantments: []
|
||||||
|
9:
|
||||||
|
material: SADDLE
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
10:
|
||||||
|
material: GOLDEN_APPLE
|
||||||
|
amount: 2
|
||||||
|
enchantments: []
|
||||||
|
11:
|
||||||
|
material: FIREWORK_ROCKET
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
12:
|
||||||
|
material: DIAMOND
|
||||||
|
amount: 5
|
||||||
|
enchantments: []
|
||||||
|
13:
|
||||||
|
material: ENCHANTED_GOLDEN_APPLE
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
14:
|
||||||
|
material: GOLD_BLOCK
|
||||||
|
amount: 4
|
||||||
|
enchantments: []
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Geburtstage
|
||||||
|
# =============================================================================
|
||||||
|
# Spieler tragen ihren Geburtstag (ohne Jahr) mit /ak birthday set <TT.MM> ein und holen
|
||||||
|
# ihr Geschenk mit /ak birthday claim ab. Einmal pro Jahr, am Geburtstag und bis zu
|
||||||
|
# grace_days Tage danach.
|
||||||
|
birthday:
|
||||||
|
enabled: true
|
||||||
|
# Wie viele Tage nach dem Geburtstag das Geschenk noch abholbar ist.
|
||||||
|
grace_days: 2
|
||||||
|
# Dürfen Spieler ihren Geburtstag nach dem Eintragen selbst ändern/löschen?
|
||||||
|
# false = nur Admins (/ak admin birthday set|remove <Spieler>), verhindert Mehrfach-Abholung.
|
||||||
|
allow_change: false
|
||||||
|
# Nachricht an alle Spieler, wenn jemand sein Geschenk abholt.
|
||||||
|
broadcast: true
|
||||||
|
# Konsolenbefehle, die zusätzlich ausgeführt werden (%player% = Spielername), z.B. "eco give %player% 500"
|
||||||
|
commands: []
|
||||||
|
rewards:
|
||||||
|
1:
|
||||||
|
material: CAKE
|
||||||
|
amount: 1
|
||||||
|
enchantments: []
|
||||||
|
2:
|
||||||
|
material: COOKIE
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
3:
|
||||||
|
material: GOLDEN_APPLE
|
||||||
|
amount: 3
|
||||||
|
enchantments: []
|
||||||
|
4:
|
||||||
|
material: DIAMOND
|
||||||
|
amount: 5
|
||||||
|
enchantments: []
|
||||||
|
5:
|
||||||
|
material: FIREWORK_ROCKET
|
||||||
|
amount: 16
|
||||||
|
enchantments: []
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ prefix: "&6[Adventskalender] &r"
|
|||||||
gui:
|
gui:
|
||||||
# Der Titel des Adventskalender-Inventars.
|
# Der Titel des Adventskalender-Inventars.
|
||||||
title: "&6✦ Adventskalender 2026 ✦"
|
title: "&6✦ Adventskalender 2026 ✦"
|
||||||
|
default_name: "&6Türchen #%day%"
|
||||||
|
click_open: "&8» &eKlicke zum Öffnen!"
|
||||||
|
locked_name: "&c&lTag #%day%"
|
||||||
|
locked_status: "&8» &7Noch verschlossen"
|
||||||
|
locked_hint: "&7Komme am %date% wieder!"
|
||||||
|
claimed_status: "&8» &6Bereits abgeholt"
|
||||||
|
claimed_hint: "&aDu hast dieses Geschenk bereits geöffnet! ✔"
|
||||||
|
|
||||||
# --- Nachrichten an Spieler ---
|
# --- Nachrichten an Spieler ---
|
||||||
# Verfügbare Platzhalter:
|
# Verfügbare Platzhalter:
|
||||||
@@ -22,16 +29,34 @@ messages:
|
|||||||
day_not_available: "&cDieses Türchen ist noch fest verschlossen. Gedulde dich noch etwas!"
|
day_not_available: "&cDieses Türchen ist noch fest verschlossen. Gedulde dich noch etwas!"
|
||||||
day_already_claimed: "&7Du hast dieses Geschenk bereits abgeholt."
|
day_already_claimed: "&7Du hast dieses Geschenk bereits abgeholt."
|
||||||
global_already_claimed: "&cDieses Geschenk wurde heute bereits von jemand anderem abgeholt!"
|
global_already_claimed: "&cDieses Geschenk wurde heute bereits von jemand anderem abgeholt!"
|
||||||
|
no_events: "&cEs ist derzeit kein Kalender aktiv."
|
||||||
|
unknown_event: "&cUnbekanntes Event '%event%'. Verfügbar: &e%events%"
|
||||||
|
db_error: "&cDie Datenbank ist gerade nicht erreichbar. Bitte versuche es später erneut."
|
||||||
|
reward_error: "&cDie Belohnung für Tag %day% ist falsch konfiguriert. Bitte melde das einem Admin."
|
||||||
|
|
||||||
# --- Admin-Nachrichten ---
|
# --- Admin-Nachrichten ---
|
||||||
admin:
|
admin:
|
||||||
|
all_events: "alle Events"
|
||||||
reload_success: "&aKonfiguration und Sprachdateien wurden erfolgreich neu geladen."
|
reload_success: "&aKonfiguration und Sprachdateien wurden erfolgreich neu geladen."
|
||||||
test_mode_on: "&e[Admin] &7Der Test-Modus wurde auf Tag &6%day% &7gesetzt."
|
test_mode_on: "&e[Admin] &7Test-Modus (%event%): Tag &6%day% &7gesetzt."
|
||||||
test_mode_off: "&e[Admin] &7Test-Modus deaktiviert. Es wird wieder das reale Datum genutzt."
|
test_mode_off: "&e[Admin] &7Test-Modus (%event%) deaktiviert. Es wird wieder das reale Datum genutzt."
|
||||||
player_not_found: "&cDer Spieler '%player%' wurde nicht gefunden."
|
player_not_found: "&cDer Spieler '%player%' wurde nicht gefunden."
|
||||||
invalid_day: "&cUngültiger Tag. Bitte wähle eine Zahl zwischen 1 und 24."
|
invalid_day: "&cUngültiger Tag. Bitte wähle eine Zahl zwischen 1 und 24."
|
||||||
|
reset_day_global: "&a[Admin] &7Tag &6%day% &7global zurückgesetzt (%event%)."
|
||||||
|
reset_day_player: "&a[Admin] &7Tag &6%day% &7für &e%player% &7zurückgesetzt (%event%)."
|
||||||
|
reset_all: "&a[Admin] &7Kompletter Reset durchgeführt (%event%)."
|
||||||
|
reset_player: "&a[Admin] &7Daten für &e%player% &7gelöscht (%event%)."
|
||||||
|
reset_offline: "&a[Admin] &7Offline-Daten für &e%player% &7gelöscht (%event%)."
|
||||||
|
help:
|
||||||
|
- "&8&m---&r &6Advent Admin &8&m---"
|
||||||
|
- "&e/ak admin reload &7- Config neu laden"
|
||||||
|
- "&e/ak admin test [event] <Tag/reset> &7- Datum simulieren"
|
||||||
|
- "&e/ak admin resetday [event] <global/Spieler> <Tag> &7- Tag löschen"
|
||||||
|
- "&e/ak admin reset [event] <global/Spieler> &7- Alles löschen"
|
||||||
|
- "&7Ohne [event] gilt der Befehl für alle Events."
|
||||||
|
- "&e/ak admin birthday <set|remove|reset> <Spieler> [TT.MM] &7- Geburtstage verwalten"
|
||||||
|
|
||||||
# --- Belohnungs-Texte im GUI ---
|
# --- Belohnungs-Texte im GUI (Advent) ---
|
||||||
# Diese Schlüssel werden vom AdventInventory genutzt, um die Items im Menü zu benennen.
|
# Diese Schlüssel werden vom AdventInventory genutzt, um die Items im Menü zu benennen.
|
||||||
rewards:
|
rewards:
|
||||||
day_1:
|
day_1:
|
||||||
@@ -106,3 +131,177 @@ rewards:
|
|||||||
day_24:
|
day_24:
|
||||||
name: "&cTag 24: Das große Finale!"
|
name: "&cTag 24: Das große Finale!"
|
||||||
lore: ["&7Frohe Weihnachten!", "&7Feiere mit einem riesigen Feuerwerk!"]
|
lore: ["&7Frohe Weihnachten!", "&7Feiere mit einem riesigen Feuerwerk!"]
|
||||||
|
|
||||||
|
# --- Weitere Events: Titel und Belohnungs-Texte ---
|
||||||
|
events:
|
||||||
|
halloween:
|
||||||
|
prefix: "&6[Halloween] &r"
|
||||||
|
title: "&6✦ Halloween-Kalender ✦"
|
||||||
|
rewards:
|
||||||
|
day_1:
|
||||||
|
name: "&6Kürbiskuchen"
|
||||||
|
lore: ["&7Süßes oder Saures?"]
|
||||||
|
day_2:
|
||||||
|
name: "&6Leuchtende Kürbisse"
|
||||||
|
lore: ["&7Gegen die Dunkelheit."]
|
||||||
|
day_3:
|
||||||
|
name: "&fKnochenvorrat"
|
||||||
|
lore: ["&7Für den treuen Wolf."]
|
||||||
|
day_4:
|
||||||
|
name: "&cSpinnenaugen"
|
||||||
|
lore: ["&7Zutat für düstere Tränke."]
|
||||||
|
day_5:
|
||||||
|
name: "&fSpinnennetze"
|
||||||
|
lore: ["&7Gruselige Deko für dein Haus."]
|
||||||
|
day_6:
|
||||||
|
name: "&8Schwarze Kerzen"
|
||||||
|
lore: ["&7Für das perfekte Ritual."]
|
||||||
|
day_7:
|
||||||
|
name: "&bSeelenfackeln"
|
||||||
|
lore: ["&7Blaues Licht in der Nacht."]
|
||||||
|
day_8:
|
||||||
|
name: "&5Gegorene Spinnenaugen"
|
||||||
|
lore: ["&7Nur für mutige Brauer."]
|
||||||
|
day_9:
|
||||||
|
name: "&fGeisterträne"
|
||||||
|
lore: ["&7Ein Hauch vom Jenseits."]
|
||||||
|
day_10:
|
||||||
|
name: "&bSeelenlaternen"
|
||||||
|
lore: ["&7Sie leuchten für verlorene Seelen."]
|
||||||
|
day_11:
|
||||||
|
name: "&7Phantom-Membranen"
|
||||||
|
lore: ["&7Beute der nächtlichen Jagd."]
|
||||||
|
day_12:
|
||||||
|
name: "&cHexenstaub"
|
||||||
|
lore: ["&7Funkelt unheimlich rot."]
|
||||||
|
day_13:
|
||||||
|
name: "&eZauberstaub"
|
||||||
|
lore: ["&7Ein Hauch von Magie."]
|
||||||
|
day_14:
|
||||||
|
name: "&6Lohenruten"
|
||||||
|
lore: ["&7Heiß und gefährlich."]
|
||||||
|
day_15:
|
||||||
|
name: "&5Enderperlen"
|
||||||
|
lore: ["&7Verschwinde wie ein Geist."]
|
||||||
|
day_16:
|
||||||
|
name: "&fSkelettschädel"
|
||||||
|
lore: ["&7Ein Totenkopf als Deko."]
|
||||||
|
day_17:
|
||||||
|
name: "&2Zombiekopf"
|
||||||
|
lore: ["&7Er starrt dich an..."]
|
||||||
|
day_18:
|
||||||
|
name: "&aCreeperkopf"
|
||||||
|
lore: ["&7Zzzzisch... nur Deko."]
|
||||||
|
day_19:
|
||||||
|
name: "&6Goldäpfel"
|
||||||
|
lore: ["&7Stärkung nach der Nacht."]
|
||||||
|
day_20:
|
||||||
|
name: "&bGeisterjäger-Schwert"
|
||||||
|
lore: ["&7Schlag zu gegen Untote."]
|
||||||
|
day_21:
|
||||||
|
name: "&fKnochenbogen"
|
||||||
|
lore: ["&7Schieß die Geister weg."]
|
||||||
|
day_22:
|
||||||
|
name: "&8Netherit-Schrott"
|
||||||
|
lore: ["&7Aus den Tiefen des Nethers."]
|
||||||
|
day_23:
|
||||||
|
name: "&8Wither-Rosen"
|
||||||
|
lore: ["&7Schön und tödlich."]
|
||||||
|
day_24:
|
||||||
|
name: "&aErfahrungsfläschchen"
|
||||||
|
lore: ["&7Schnell Level sammeln."]
|
||||||
|
day_25:
|
||||||
|
name: "&dTotem des Unvergänglichen"
|
||||||
|
lore: ["&7Gib dem Tod keine Chance."]
|
||||||
|
day_26:
|
||||||
|
name: "&eGrusel-Namensschilder"
|
||||||
|
lore: ["&7Benenne deine Kreaturen."]
|
||||||
|
day_27:
|
||||||
|
name: "&fSchallplatte - 13"
|
||||||
|
lore: ["&7Unheimliche Klänge."]
|
||||||
|
day_28:
|
||||||
|
name: "&bDiamanten"
|
||||||
|
lore: ["&7Ein glänzender Schatz."]
|
||||||
|
day_29:
|
||||||
|
name: "&8Wither-Skelettschädel"
|
||||||
|
lore: ["&7Beschwört nichts Gutes."]
|
||||||
|
day_30:
|
||||||
|
name: "&eNetherstern"
|
||||||
|
lore: ["&7Das Herz der Nacht."]
|
||||||
|
day_31:
|
||||||
|
name: "&c&lHalloween-Finale!"
|
||||||
|
lore: ["&7Netherit zum Gruseln - frohes Halloween!"]
|
||||||
|
easter:
|
||||||
|
prefix: "&e[Ostern] &r"
|
||||||
|
title: "&e✦ Oster-Kalender ✦"
|
||||||
|
rewards:
|
||||||
|
day_1:
|
||||||
|
name: "&fOstereier"
|
||||||
|
lore: ["&7Frisch vom Hasen."]
|
||||||
|
day_2:
|
||||||
|
name: "&6Möhren"
|
||||||
|
lore: ["&7Des Hasen Lieblingsessen."]
|
||||||
|
day_3:
|
||||||
|
name: "&eGoldene Karotten"
|
||||||
|
lore: ["&7Ein edler Snack."]
|
||||||
|
day_4:
|
||||||
|
name: "&fHasenpfote"
|
||||||
|
lore: ["&7Bringt Glück!"]
|
||||||
|
day_5:
|
||||||
|
name: "&6Schoko-Kekse"
|
||||||
|
lore: ["&7Süße Osterleckerei."]
|
||||||
|
day_6:
|
||||||
|
name: "&eHonigflaschen"
|
||||||
|
lore: ["&7Süß wie der Frühling."]
|
||||||
|
day_7:
|
||||||
|
name: "&dOsterkuchen"
|
||||||
|
lore: ["&7Mit viel Liebe gebacken."]
|
||||||
|
day_8:
|
||||||
|
name: "&dFrühlingsblumen"
|
||||||
|
lore: ["&7Ein bunter Strauß."]
|
||||||
|
day_9:
|
||||||
|
name: "&6Osterhasen-Sattel"
|
||||||
|
lore: ["&7Hoppel los!"]
|
||||||
|
day_10:
|
||||||
|
name: "&6Goldäpfel"
|
||||||
|
lore: ["&7Eine Belohnung für Eiersucher."]
|
||||||
|
day_11:
|
||||||
|
name: "&cFrühlingsfeuerwerk"
|
||||||
|
lore: ["&7Lass es knallen!"]
|
||||||
|
day_12:
|
||||||
|
name: "&bDiamanten"
|
||||||
|
lore: ["&7Im Osternest versteckt."]
|
||||||
|
day_13:
|
||||||
|
name: "&6Verzauberter Goldapfel"
|
||||||
|
lore: ["&7Der Preis fürs Eiersuchen."]
|
||||||
|
day_14:
|
||||||
|
name: "&e&lDas goldene Osterei!"
|
||||||
|
lore: ["&7Frohe Ostern!"]
|
||||||
|
|
||||||
|
# --- Geburtstage ---
|
||||||
|
birthday:
|
||||||
|
prefix: "&6[Geburtstag] &r"
|
||||||
|
disabled: "&cGeburtstags-Geschenke sind deaktiviert."
|
||||||
|
help:
|
||||||
|
- "&8&m---&r &6Geburtstag &8&m---"
|
||||||
|
- "&e/ak birthday set <TT.MM> &7- Geburtstag eintragen (z.B. 15.03)"
|
||||||
|
- "&e/ak birthday claim &7- Geschenk abholen (am Geburtstag und %days% Tage danach)"
|
||||||
|
invalid_date: "&cUngültiges Datum. Bitte nutze das Format &eTT.MM &c(z.B. 15.03)."
|
||||||
|
set_success: "&aDein Geburtstag wurde auf den &e%date% &agesetzt. Am Tag selbst und %days% Tage danach kannst du dir mit &e/ak birthday claim &adein Geschenk abholen!"
|
||||||
|
already_set: "&cDu hast deinen Geburtstag bereits eingetragen (&e%date%&c). Bei Fehlern wende dich an einen Admin."
|
||||||
|
info: "&7Dein Geburtstag: &e%date%"
|
||||||
|
info_none: "&7Du hast noch keinen Geburtstag eingetragen: &e/ak birthday set <TT.MM>"
|
||||||
|
removed: "&7Dein Geburtstag wurde gelöscht."
|
||||||
|
change_not_allowed: "&cDu kannst deinen Geburtstag nicht selbst ändern. Bitte wende dich an einen Admin."
|
||||||
|
not_set: "&cDu hast noch keinen Geburtstag eingetragen. Nutze &e/ak birthday set <TT.MM>&c."
|
||||||
|
not_today: "&cDein Geburtstag (&e%date%&c) ist gerade nicht. Das Geschenk gibt es am Tag selbst und %days% Tage danach."
|
||||||
|
already_claimed: "&7Du hast dein Geburtstagsgeschenk dieses Jahr schon abgeholt."
|
||||||
|
reward_received: "&6&lAlles Gute zum Geburtstag! &eViel Spaß mit deinem Geschenk!"
|
||||||
|
broadcast: "&6%player% &efeiert Geburtstag - alles Gute!"
|
||||||
|
join_notice: "&6&lAlles Gute zum Geburtstag! &eKlicke hier, um dein Geschenk abzuholen!"
|
||||||
|
join_hover: "&7Führt /ak birthday claim aus"
|
||||||
|
reward_error: "&cDas Geburtstagsgeschenk ist falsch konfiguriert. Bitte melde das einem Admin."
|
||||||
|
admin_set: "&a[Admin] &7Geburtstag von &e%player% &7auf &6%date% &7gesetzt."
|
||||||
|
admin_removed: "&a[Admin] &7Geburtstag von &e%player% &7gelöscht."
|
||||||
|
admin_reset: "&a[Admin] &7Geschenk-Abholung von &e%player% &7zurückgesetzt."
|
||||||
|
admin_none: "&cFür &e%player% &cist kein Geburtstag eingetragen."
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ prefix: "&6[Advent] &r"
|
|||||||
gui:
|
gui:
|
||||||
# The title of the Advent Calendar inventory.
|
# The title of the Advent Calendar inventory.
|
||||||
title: "&6✦ Advent Calendar 2026 ✦"
|
title: "&6✦ Advent Calendar 2026 ✦"
|
||||||
|
default_name: "&6Door #%day%"
|
||||||
|
click_open: "&8» &eClick to open!"
|
||||||
|
locked_name: "&c&lDay #%day%"
|
||||||
|
locked_status: "&8» &7Still locked"
|
||||||
|
locked_hint: "&7Come back on %date%!"
|
||||||
|
claimed_status: "&8» &6Already claimed"
|
||||||
|
claimed_hint: "&aYou have already opened this gift! ✔"
|
||||||
|
|
||||||
# --- Messages to Players ---
|
# --- Messages to Players ---
|
||||||
# Available placeholders:
|
# Available placeholders:
|
||||||
@@ -22,14 +29,32 @@ messages:
|
|||||||
day_not_available: "&cThis door is still locked. You'll have to wait a bit longer!"
|
day_not_available: "&cThis door is still locked. You'll have to wait a bit longer!"
|
||||||
day_already_claimed: "&7You have already claimed this gift."
|
day_already_claimed: "&7You have already claimed this gift."
|
||||||
global_already_claimed: "&cThis gift has already been claimed by someone else today!"
|
global_already_claimed: "&cThis gift has already been claimed by someone else today!"
|
||||||
|
no_events: "&cNo calendar is active right now."
|
||||||
|
unknown_event: "&cUnknown event '%event%'. Available: &e%events%"
|
||||||
|
db_error: "&cThe database is currently unavailable. Please try again later."
|
||||||
|
reward_error: "&cThe reward for day %day% is misconfigured. Please report this to an admin."
|
||||||
|
|
||||||
# --- Admin Messages ---
|
# --- Admin Messages ---
|
||||||
admin:
|
admin:
|
||||||
|
all_events: "all events"
|
||||||
reload_success: "&aConfiguration and language files have been reloaded."
|
reload_success: "&aConfiguration and language files have been reloaded."
|
||||||
test_mode_on: "&e[Admin] &7Test mode set to day &6%day%&7."
|
test_mode_on: "&e[Admin] &7Test mode (%event%) set to day &6%day%&7."
|
||||||
test_mode_off: "&e[Admin] &7Test mode disabled. Using real-time date again."
|
test_mode_off: "&e[Admin] &7Test mode (%event%) disabled. Using real-time date again."
|
||||||
player_not_found: "&cThe player '%player%' was not found."
|
player_not_found: "&cThe player '%player%' was not found."
|
||||||
invalid_day: "&cInvalid day. Please choose a number between 1 and 24."
|
invalid_day: "&cInvalid day. Please choose a number between 1 and 24."
|
||||||
|
reset_day_global: "&a[Admin] &7Day &6%day% &7reset globally (%event%)."
|
||||||
|
reset_day_player: "&a[Admin] &7Day &6%day% &7reset for &e%player%&7 (%event%)."
|
||||||
|
reset_all: "&a[Admin] &7Full reset completed (%event%)."
|
||||||
|
reset_player: "&a[Admin] &7Data for &e%player% &7deleted (%event%)."
|
||||||
|
reset_offline: "&a[Admin] &7Offline data for &e%player% &7deleted (%event%)."
|
||||||
|
help:
|
||||||
|
- "&8&m---&r &6Advent Admin &8&m---"
|
||||||
|
- "&e/ak admin reload &7- Reload config"
|
||||||
|
- "&e/ak admin test [event] <day/reset> &7- Simulate date"
|
||||||
|
- "&e/ak admin resetday [event] <global/player> <day> &7- Reset a day"
|
||||||
|
- "&e/ak admin reset [event] <global/player> &7- Reset everything"
|
||||||
|
- "&7Without [event] the command applies to all events."
|
||||||
|
- "&e/ak admin birthday <set|remove|reset> <player> [DD.MM] &7- Manage birthdays"
|
||||||
|
|
||||||
# --- Reward Texts in GUI ---
|
# --- Reward Texts in GUI ---
|
||||||
rewards:
|
rewards:
|
||||||
@@ -105,3 +130,177 @@ rewards:
|
|||||||
day_24:
|
day_24:
|
||||||
name: "&cDay 24: The Grand Finale!"
|
name: "&cDay 24: The Grand Finale!"
|
||||||
lore: ["&7Merry Christmas!", "&7Celebrate with a huge firework!"]
|
lore: ["&7Merry Christmas!", "&7Celebrate with a huge firework!"]
|
||||||
|
|
||||||
|
# --- More events: titles and reward texts ---
|
||||||
|
events:
|
||||||
|
halloween:
|
||||||
|
prefix: "&6[Halloween] &r"
|
||||||
|
title: "&6✦ Halloween Calendar ✦"
|
||||||
|
rewards:
|
||||||
|
day_1:
|
||||||
|
name: "&6Pumpkin Pie"
|
||||||
|
lore: ["&7Trick or treat?"]
|
||||||
|
day_2:
|
||||||
|
name: "&6Jack o'Lanterns"
|
||||||
|
lore: ["&7Against the darkness."]
|
||||||
|
day_3:
|
||||||
|
name: "&fBone Stash"
|
||||||
|
lore: ["&7For your loyal wolf."]
|
||||||
|
day_4:
|
||||||
|
name: "&cSpider Eyes"
|
||||||
|
lore: ["&7An ingredient for dark potions."]
|
||||||
|
day_5:
|
||||||
|
name: "&fCobwebs"
|
||||||
|
lore: ["&7Spooky decoration for your home."]
|
||||||
|
day_6:
|
||||||
|
name: "&8Black Candles"
|
||||||
|
lore: ["&7For the perfect ritual."]
|
||||||
|
day_7:
|
||||||
|
name: "&bSoul Torches"
|
||||||
|
lore: ["&7Blue light in the night."]
|
||||||
|
day_8:
|
||||||
|
name: "&5Fermented Spider Eyes"
|
||||||
|
lore: ["&7Only for brave brewers."]
|
||||||
|
day_9:
|
||||||
|
name: "&fGhost Tears"
|
||||||
|
lore: ["&7A breath from the beyond."]
|
||||||
|
day_10:
|
||||||
|
name: "&bSoul Lanterns"
|
||||||
|
lore: ["&7They shine for lost souls."]
|
||||||
|
day_11:
|
||||||
|
name: "&7Phantom Membranes"
|
||||||
|
lore: ["&7Loot from the night hunt."]
|
||||||
|
day_12:
|
||||||
|
name: "&cWitch's Dust"
|
||||||
|
lore: ["&7Glows an eerie red."]
|
||||||
|
day_13:
|
||||||
|
name: "&eMagic Dust"
|
||||||
|
lore: ["&7A touch of magic."]
|
||||||
|
day_14:
|
||||||
|
name: "&6Blaze Rods"
|
||||||
|
lore: ["&7Hot and dangerous."]
|
||||||
|
day_15:
|
||||||
|
name: "&5Ender Pearls"
|
||||||
|
lore: ["&7Vanish like a ghost."]
|
||||||
|
day_16:
|
||||||
|
name: "&fSkeleton Skull"
|
||||||
|
lore: ["&7A skull as decoration."]
|
||||||
|
day_17:
|
||||||
|
name: "&2Zombie Head"
|
||||||
|
lore: ["&7It stares at you..."]
|
||||||
|
day_18:
|
||||||
|
name: "&aCreeper Head"
|
||||||
|
lore: ["&7Ssssss... just decoration."]
|
||||||
|
day_19:
|
||||||
|
name: "&6Golden Apples"
|
||||||
|
lore: ["&7A boost after the night."]
|
||||||
|
day_20:
|
||||||
|
name: "&bGhostbuster Sword"
|
||||||
|
lore: ["&7Strike down the undead."]
|
||||||
|
day_21:
|
||||||
|
name: "&fBone Bow"
|
||||||
|
lore: ["&7Shoot the ghosts away."]
|
||||||
|
day_22:
|
||||||
|
name: "&8Netherite Scrap"
|
||||||
|
lore: ["&7From the depths of the Nether."]
|
||||||
|
day_23:
|
||||||
|
name: "&8Wither Roses"
|
||||||
|
lore: ["&7Beautiful and deadly."]
|
||||||
|
day_24:
|
||||||
|
name: "&aBottles o' Enchanting"
|
||||||
|
lore: ["&7Gain levels fast."]
|
||||||
|
day_25:
|
||||||
|
name: "&dTotem of Undying"
|
||||||
|
lore: ["&7Give death no chance."]
|
||||||
|
day_26:
|
||||||
|
name: "&eSpooky Name Tags"
|
||||||
|
lore: ["&7Name your creatures."]
|
||||||
|
day_27:
|
||||||
|
name: "&fMusic Disc - 13"
|
||||||
|
lore: ["&7Eerie sounds."]
|
||||||
|
day_28:
|
||||||
|
name: "&bDiamonds"
|
||||||
|
lore: ["&7A shiny treasure."]
|
||||||
|
day_29:
|
||||||
|
name: "&8Wither Skeleton Skull"
|
||||||
|
lore: ["&7Summons nothing good."]
|
||||||
|
day_30:
|
||||||
|
name: "&eNether Star"
|
||||||
|
lore: ["&7The heart of the night."]
|
||||||
|
day_31:
|
||||||
|
name: "&c&lHalloween Finale!"
|
||||||
|
lore: ["&7Spooky netherite - Happy Halloween!"]
|
||||||
|
easter:
|
||||||
|
prefix: "&e[Easter] &r"
|
||||||
|
title: "&e✦ Easter Calendar ✦"
|
||||||
|
rewards:
|
||||||
|
day_1:
|
||||||
|
name: "&fEaster Eggs"
|
||||||
|
lore: ["&7Fresh from the bunny."]
|
||||||
|
day_2:
|
||||||
|
name: "&6Carrots"
|
||||||
|
lore: ["&7The bunny's favorite food."]
|
||||||
|
day_3:
|
||||||
|
name: "&eGolden Carrots"
|
||||||
|
lore: ["&7A noble snack."]
|
||||||
|
day_4:
|
||||||
|
name: "&fRabbit's Foot"
|
||||||
|
lore: ["&7Brings luck!"]
|
||||||
|
day_5:
|
||||||
|
name: "&6Chocolate Cookies"
|
||||||
|
lore: ["&7A sweet Easter treat."]
|
||||||
|
day_6:
|
||||||
|
name: "&eHoney Bottles"
|
||||||
|
lore: ["&7Sweet as spring."]
|
||||||
|
day_7:
|
||||||
|
name: "&dEaster Cake"
|
||||||
|
lore: ["&7Baked with lots of love."]
|
||||||
|
day_8:
|
||||||
|
name: "&dSpring Flowers"
|
||||||
|
lore: ["&7A colorful bouquet."]
|
||||||
|
day_9:
|
||||||
|
name: "&6Easter Bunny Saddle"
|
||||||
|
lore: ["&7Hop to it!"]
|
||||||
|
day_10:
|
||||||
|
name: "&6Golden Apples"
|
||||||
|
lore: ["&7A reward for egg hunters."]
|
||||||
|
day_11:
|
||||||
|
name: "&cSpring Fireworks"
|
||||||
|
lore: ["&7Let it bang!"]
|
||||||
|
day_12:
|
||||||
|
name: "&bDiamonds"
|
||||||
|
lore: ["&7Hidden in the Easter nest."]
|
||||||
|
day_13:
|
||||||
|
name: "&6Enchanted Golden Apple"
|
||||||
|
lore: ["&7The prize for egg hunting."]
|
||||||
|
day_14:
|
||||||
|
name: "&e&lThe Golden Easter Egg!"
|
||||||
|
lore: ["&7Happy Easter!"]
|
||||||
|
|
||||||
|
# --- Birthdays ---
|
||||||
|
birthday:
|
||||||
|
prefix: "&6[Birthday] &r"
|
||||||
|
disabled: "&cBirthday gifts are disabled."
|
||||||
|
help:
|
||||||
|
- "&8&m---&r &6Birthday &8&m---"
|
||||||
|
- "&e/ak birthday set <DD.MM> &7- Set your birthday (e.g. 15.03)"
|
||||||
|
- "&e/ak birthday claim &7- Claim your gift (on your birthday and %days% days after)"
|
||||||
|
invalid_date: "&cInvalid date. Please use the format &eDD.MM &c(e.g. 15.03)."
|
||||||
|
set_success: "&aYour birthday has been set to &e%date%&a. On the day and for %days% days after you can claim your gift with &e/ak birthday claim&a!"
|
||||||
|
already_set: "&cYou have already set your birthday (&e%date%&c). Contact an admin if it is wrong."
|
||||||
|
info: "&7Your birthday: &e%date%"
|
||||||
|
info_none: "&7You have not set a birthday yet: &e/ak birthday set <DD.MM>"
|
||||||
|
removed: "&7Your birthday has been deleted."
|
||||||
|
change_not_allowed: "&cYou cannot change your birthday yourself. Please contact an admin."
|
||||||
|
not_set: "&cYou have not set a birthday yet. Use &e/ak birthday set <DD.MM>&c."
|
||||||
|
not_today: "&cYour birthday (&e%date%&c) is not right now. The gift is available on the day and for %days% days after."
|
||||||
|
already_claimed: "&7You have already claimed your birthday gift this year."
|
||||||
|
reward_received: "&6&lHappy Birthday! &eEnjoy your gift!"
|
||||||
|
broadcast: "&6%player% &eis celebrating a birthday - happy birthday!"
|
||||||
|
join_notice: "&6&lHappy Birthday! &eClick here to claim your gift!"
|
||||||
|
join_hover: "&7Runs /ak birthday claim"
|
||||||
|
reward_error: "&cThe birthday gift is misconfigured. Please report this to an admin."
|
||||||
|
admin_set: "&a[Admin] &7Birthday of &e%player% &7set to &6%date%&7."
|
||||||
|
admin_removed: "&a[Admin] &7Birthday of &e%player% &7deleted."
|
||||||
|
admin_reset: "&a[Admin] &7Gift claim of &e%player% &7reset."
|
||||||
|
admin_none: "&cNo birthday is set for &e%player%&c."
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: Adventskalender
|
name: Adventskalender
|
||||||
version: '1.0.2'
|
version: '1.0.3'
|
||||||
main: de.mviper.adventskalender.Adventskalender
|
main: de.mviper.adventskalender.Adventskalender
|
||||||
api-version: 1.20
|
api-version: 1.20
|
||||||
author: M_Viper
|
author: M_Viper
|
||||||
@@ -18,6 +18,9 @@ permissions:
|
|||||||
adventskalender.use:
|
adventskalender.use:
|
||||||
description: "Erlaubt dem Spieler, den Adventskalender zu benutzen."
|
description: "Erlaubt dem Spieler, den Adventskalender zu benutzen."
|
||||||
default: true
|
default: true
|
||||||
|
adventskalender.birthday:
|
||||||
|
description: "Erlaubt dem Spieler, seinen Geburtstag einzutragen und das Geburtstagsgeschenk abzuholen."
|
||||||
|
default: true
|
||||||
adventskalender.admin:
|
adventskalender.admin:
|
||||||
description: "Erlaubt den Zugriff auf alle Admin-Befehle."
|
description: "Erlaubt den Zugriff auf alle Admin-Befehle."
|
||||||
default: op
|
default: op
|
||||||
Reference in New Issue
Block a user