13 Commits
1.0.1 ... main

18 changed files with 1038 additions and 686 deletions

186
README.md
View File

@@ -1,132 +1,118 @@
# 🎄 Minecraft Adventskalender Plugin
<div align="center">
Ein hochgradig konfigurierbares Adventskalender-Plugin für **Spigot/Paper-Server**, das dir die volle Kontrolle über Belohnungen, Sprache und Funktionen gibt.
Perfekt, um die Weihnachtszeit auf deinem Server zu versüßen!
# ❄️ Minecraft Adventskalender ❄️
*Bringe festliche Stimmung auf deinen Server mit einem hochgradig anpassbaren System*
![Version](https://img.shields.io/badge/Version-1.18.x--1.21.x-green.svg)
![Java](https://img.shields.io/badge/Java-17+-orange.svg)
![Update Checker](https://img.shields.io/badge/Update--Checker-Klickbar-blue.svg)
![Type](https://img.shields.io/badge/Type-Event-red.svg)
</div>
---
## 🖥️ Adventskalender GUI
## Über Minecraft Adventskalender
![Adventskalender GUI](https://git.viper.ipv64.net/M_Viper/Advenskalender/raw/branch/main/img/03.png)
![Adventskalender GUI](https://git.viper.ipv64.net/M_Viper/Advenskalender/raw/branch/main/img/04.png)
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!
---
## ✨ Features
## ✨ Exklusive Features
- **Anpassbare Belohnungen**
Definiere in der `config.yml` für jeden Tag ein eigenes Item, inklusive:
- Menge
- Name
- Lore
- Verzauberungen
- **Globaler oder individueller Kalender**
Entscheide, ob:
- jeder Spieler seinen eigenen Fortschritt hat oder
- ein Türchen serverweit nur einmal geöffnet werden kann
- **Sound & Partikel**
Angenehme Sound- und Partikeleffekte beim Öffnen eines Türchens.
- **Mehrsprachig**
Unterstützt **Deutsch** und **Englisch**.
Weitere Sprachen können einfach hinzugefügt werden.
- **Admin-Befehle**
Konfigurationen neu laden oder Türchen gezielt für Spieler öffnen.
- **Permissions-System**
Feingranulare Kontrolle über Spieler- und Admin-Rechte.
- **PlaceholderAPI-Unterstützung**
Zeige Statistiken:
- auf Schildern
- in der Tab-Liste
- mit Hologramm-Plugins
- Scoreboard
und vieles mehr.
- **Datenpersistenz**
Spielerfortschritt bleibt auch nach Server-Neustarts erhalten.
- **Vollständig Anpassbare Belohnungen:** Definiere für jeden Tag individuelle Items mit Menge, Name, Lore und Verzauberungen.
- **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.).
- **Simulations Modus (Admin-Test):** Teste alle Türchen vorab durch Datums-Simulation, ohne deine Systemzeit zu ändern. Inklusive einfachem Reset auf das Echtzeit-Datum.
- **Präzises Reset-System:** Setze einzelne Tage oder komplette Fortschritte zurück wahlweise für einzelne Spieler oder global für den ganzen Server.
- **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...).
---
## 📸 Screenshots
## 🎄 Adventskalender GUI
<div align="center">
<img src="https://git.viper.ipv64.net/M_Viper/Advenskalender/raw/branch/main/img/03.png" width="400" alt="Kalender GUI 1">
<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
### ▶ Spieler-Befehle
**`/adventskalender`** (Aliase: `/ak`, `/advent`, `/kalender`)
- **Beschreibung:** Öffnet die Kalender-Oberfläche.
- **Berechtigung:** `adventskalender.use`
### ▶ Admin-Befehle
**`/ak admin test <1-24 | reset>`**
- **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>`**
- **Beschreibung:** Setzt den Status eines spezifischen Tages für einen Spieler oder den gesamten Server zurück.
- **Berechtigung:** `adventskalender.admin`
**`/ak admin reset <global | Spieler>`**
- **Beschreibung:** Löscht den kompletten Fortschritt (alle Tage) für einen Spieler oder global.
- **Berechtigung:** `adventskalender.admin`
**`/ak admin reload`**
- **Beschreibung:** Lädt Config, Sprachdateien und Datenbank-Verbindung im laufenden Betrieb neu.
- **Berechtigung:** `adventskalender.admin`
---
## 📊 PlaceholderAPI (PAPI)
- **`%ak_c%`** - Anzahl bereits geöffneter Türchen.
- **`%ak_n%`** - Nummer des nächsten verfügbaren Türchens.
- **`%ak_d%`** - Verbleibende Tage bis zum 25. Dezember.
### Placeholder auf einem Schild
Ein Schild, das die Placeholder des Plugins anzeigt.
<div align="center">
<img src="https://git.viper.ipv64.net/M_Viper/Advenskalender/raw/branch/main/img/02.png" width="450" alt="Adventskalender Placeholder">
<img src="https://git.viper.ipv64.net/M_Viper/Advenskalender/raw/branch/main/img/02.png" alt="Placeholder Beispiel">
*Ein Schild, das die Placeholder des Plugins anzeigt.*
</div>
---
## 🚀 Installation
## 💬 Support & Community
1. Lade die neueste Version des Plugins als **`.jar`-Datei** herunter.
2. Platziere die Datei im Ordner:
Du hast Fragen oder Feedback? Tritt unserem Discord bei!
/plugins
<div align="center">
3. Stelle sicher, dass **PlaceholderAPI** installiert ist (Pflichtabhängigkeit).
4. Starte oder starte den Server neu.
5. Passe die Konfigurationsdateien an: /plugins/Adventskalender/
[![Discord Support](https://img.shields.io/badge/Discord-Support-7289DA?style=for-the-badge&logo=discord)](https://discord.com/invite/FdRs4BRd8D)
*Klicke auf den Button für direkten Support durch die Community!*
</div>
---
## ⚙️ Konfiguration
## 🔧 Technische Details
Das Plugin verwendet drei Hauptdateien:
- **`config.yml`**
- Sprache
- Kalender-Typ (global / individuell)
- Belohnungen für alle 24 Tage
- **`messages_de.yml` / `messages_en.yml`**
- GUI-Titel
- Chat-Nachrichten
- Systemmeldungen
Diese Dateien können frei angepasst oder übersetzt werden.
- **Kompatibilität:** Paper, Spigot, Purpur (1.18.x - 1.21.x)
- **Smart Caching:** Minimale Serverlast durch asynchrone Datenbankaufrufe.
- **Join-Delay:** Update-Meldungen erscheinen erst nach 2 Sekunden, um den Chat-Spam beim Joinen zu umgehen.
---
## 📋 Befehle & Berechtigungen
<div align="center">
### Befehle
*Viper Plugins © 2026 - Effizienz und Magie für deinen Server*
| Befehl | Alias | Beschreibung | Berechtigung |
|------|------|-------------|--------------|
| `/adventskalender` | `/advent`, `/kalender`, `/ak` | Öffnet den Adventskalender | `adventskalender.use` |
| `/adventskalender admin reload` | `/ak admin reload` | Lädt Konfigurations- & Sprachdateien neu | `adventskalender.admin` |
| `/adventskalender admin open <spieler> <tag>` | `/ak admin open ...` | Öffnet ein Türchen für einen Spieler | `adventskalender.admin` |
### Berechtigungen
| Berechtigung | Standard | Beschreibung |
|-------------|----------|--------------|
| `adventskalender.use` | `true` | Erlaubt das Öffnen des Kalenders |
| `adventskalender.admin` | `op` | Erlaubt alle Admin-Befehle |
---
## 🔗 Placeholders
Alle Placeholder sind **PlaceholderAPI-kompatibel** und kurz gehalten.
| Placeholder | Beschreibung |
|------------|--------------|
| `%ak_c%` | Anzahl geöffneter Türchen |
| `%ak_n%` | Nächstes verfügbares Türchen |
| `%ak_d%` | Verbleibende Tage bis 25. Dezember |
### Beispiel für ein Schild
Dein Fortschritt:
%ak_c%/24 Türchen
---
</div>

View File

@@ -6,7 +6,7 @@
<groupId>de.mviper</groupId>
<artifactId>adventskalender</artifactId>
<version>1.0.0</version>
<version>1.0.1</version>
<packaging>jar</packaging>
<properties>

View File

@@ -1,56 +0,0 @@
package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.Material;
public class AdminCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) {
Adventskalender.getInstance().reloadConfig();
LanguageManager.setup();
sender.sendMessage(LanguageManager.getString("messages.admin.reload_success"));
return true;
}
if (args.length == 3 && args[0].equalsIgnoreCase("open")) {
Player target = Bukkit.getPlayer(args[1]);
if (target == null) {
sender.sendMessage(LanguageManager.getString("messages.admin.player_not_found").replace("%player%", args[1]));
return true;
}
try {
int day = Integer.parseInt(args[2]);
if (day < 1 || day > 24) {
sender.sendMessage(LanguageManager.getString("messages.admin.invalid_day"));
return true;
}
if (target.getInventory().firstEmpty() == -1) {
target.getWorld().dropItemNaturally(target.getLocation(), AdventInventory.getRewardItem(day));
} else {
target.getInventory().addItem(AdventInventory.getRewardItem(day));
}
CalendarData.setClaimed(target, day);
sender.sendMessage(LanguageManager.getString("messages.admin.open_success").replace("%player%", target.getName()).replace("%day%", String.valueOf(day)));
target.sendMessage(LanguageManager.getString("messages.reward_received").replace("%day%", String.valueOf(day)));
} catch (NumberFormatException e) {
sender.sendMessage(LanguageManager.getString("messages.admin.invalid_day"));
}
return true;
}
sender.sendMessage("§cUsage: /adventskalender admin reload");
sender.sendMessage("§cUsage: /adventskalender admin open <player> <day>");
return true;
}
}

View File

@@ -2,61 +2,68 @@ package de.mviper.adventskalender;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.time.LocalDate;
import java.time.Month;
public class AdventCalendarExpansion extends PlaceholderExpansion {
@Override
public String getIdentifier() {
// Der Identifier ist jetzt nur noch "ak"
public @NotNull String getIdentifier() {
return "ak";
}
@Override
public String getAuthor() {
return "mviper";
public @NotNull String getAuthor() {
return "M_Viper";
}
@Override
public String getVersion() {
return "1.1.0";
public @NotNull String getVersion() {
return "2.0.0";
}
@Override
public boolean canRegister() {
return true;
public boolean persist() {
return true; // Expansion bleibt nach PAPI-Reload registriert
}
@Override
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)
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_claimed% - Anzahl der bereits geöffneten Türchen
case "claimed":
return String.valueOf(DataHandler.getClaimedDays(player).size());
// %ak_day% - Der aktuelle Tag des Adventskalenders (1-24)
case "day":
return String.valueOf(currentDay);
// %ak_has_claimed_today% - Gibt "Ja" oder "Nein" zurück, ob heute schon geöffnet wurde
case "has_claimed_today":
if (currentDay == 0) return "Keine Adventszeit";
return DataHandler.hasClaimed(player, currentDay) ? "Ja" : "Nein";
// %ak_remaining% - Wie viele Türchen sind noch offen (für diesen Spieler)?
case "remaining":
int claimedCount = DataHandler.getClaimedDays(player).size();
return String.valueOf(24 - claimedCount);
// %ak_is_global% - Zeigt an, ob der globale Modus aktiv ist
case "is_global":
boolean isGlobal = Adventskalender.getInstance().getConfig().getBoolean("calendar.use_global_calendar", false);
return isGlobal ? "Global" : "Spieler-basiert";
}
// Die Platzhalter sind jetzt viel kürzer
switch (identifier) {
case "c": // Geöffnet (claimed)
return String.valueOf(CalendarData.getClaimedCount(player));
case "n": // Nächstes (next)
for (int day = 1; day <= 24; day++) {
if (!CalendarData.hasClaimed(player, day)) {
return String.valueOf(day);
}
}
return "None";
case "d": // Tage (days)
LocalDate today = LocalDate.now();
LocalDate christmas = LocalDate.of(today.getYear(), Month.DECEMBER, 25);
if (today.isAfter(christmas)) {
christmas = christmas.plusYears(1);
}
return String.valueOf((int) java.time.temporal.ChronoUnit.DAYS.between(today, christmas));
default:
return null;
}
return null; // Placeholder unbekannt
}
}

View File

@@ -1,23 +1,18 @@
package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.stream.Collectors;
public class AdventCommand implements CommandExecutor {
public class AdventCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// --- Admin-Befehle (/ak admin ...) ---
if (args.length >= 1 && args[0].equalsIgnoreCase("admin")) {
// Prüfen, ob der Spieler die Admin-Berechtigung hat
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
if (args.length > 0 && args[0].equalsIgnoreCase("admin")) {
if (!sender.hasPermission("adventskalender.admin")) {
sender.sendMessage(LanguageManager.getString("messages.no_permission"));
sender.sendMessage(LanguageManager.getMessage("messages.no_permission"));
return true;
}
@@ -25,63 +20,141 @@ public class AdventCommand implements CommandExecutor {
if (args.length == 2 && args[1].equalsIgnoreCase("reload")) {
Adventskalender.getInstance().reloadConfig();
LanguageManager.setup();
sender.sendMessage(LanguageManager.getString("messages.admin.reload_success"));
DataHandler.setup();
sender.sendMessage(LanguageManager.getMessage("messages.admin.reload_success"));
return true;
}
// /ak admin open <player> <day>
if (args.length == 4 && args[1].equalsIgnoreCase("open")) {
Player target = Bukkit.getPlayer(args[2]);
if (target == null) {
sender.sendMessage(LanguageManager.getString("messages.admin.player_not_found").replace("%player%", args[2]));
// /ak admin test <Tag/reset>
if (args.length == 3 && args[1].equalsIgnoreCase("test")) {
if (args[2].equalsIgnoreCase("reset")) {
Adventskalender.getInstance().getConfig().set("general.test_day", 0);
Adventskalender.getInstance().saveConfig();
sender.sendMessage("§e[Admin] §7Test-Modus §cbeendet§7. Das echte Datum wird nun verwendet.");
return true;
}
try {
int day = Integer.parseInt(args[3]);
int day = Integer.parseInt(args[2]);
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;
}
ItemStack reward = AdventInventory.getRewardItem(day);
if (reward != null && reward.getType() != Material.AIR) {
if (target.getInventory().firstEmpty() == -1) {
target.getWorld().dropItemNaturally(target.getLocation(), reward);
} else {
target.getInventory().addItem(reward);
}
}
CalendarData.setClaimed(target, day);
sender.sendMessage(LanguageManager.getString("messages.admin.open_success").replace("%player%", target.getName()).replace("%day%", String.valueOf(day)));
target.sendMessage(LanguageManager.getString("messages.reward_received").replace("%day%", String.valueOf(day)));
Adventskalender.getInstance().getConfig().set("general.test_day", day);
Adventskalender.getInstance().saveConfig();
sender.sendMessage("§e[Admin] §7Test-Tag auf §6" + day + " §7gesetzt.");
} catch (NumberFormatException e) {
sender.sendMessage(LanguageManager.getString("messages.admin.invalid_day"));
sender.sendMessage("§cBitte gib eine gültige Zahl (1-24) oder 'reset' an.");
}
return true;
}
// Falsche Nutzung der Admin-Befehle
sender.sendMessage("§cUsage: /ak admin reload");
sender.sendMessage("§cUsage: /ak admin open <player> <day>");
// /ak admin resetday <global/Spieler> <Tag>
if (args.length == 4 && args[1].equalsIgnoreCase("resetday")) {
String target = args[2];
try {
int day = Integer.parseInt(args[3]);
if (day < 1 || day > 24) throw new NumberFormatException();
if (target.equalsIgnoreCase("global")) {
DataHandler.GlobalDataManager.resetGlobalDay(day);
sender.sendMessage("§a[Admin] §7Tag §6" + day + " §7global zurückgesetzt.");
} else {
DataHandler.resetPlayerDay(target, day);
sender.sendMessage("§a[Admin] §7Tag §6" + day + " §7für §e" + target + " §7zurückgesetzt.");
}
} catch (NumberFormatException e) {
sender.sendMessage("§cTag muss zwischen 1 und 24 liegen.");
}
return true;
}
// --- Hauptbefehl (/ak) ---
if (!(sender instanceof Player)) {
sender.sendMessage(LanguageManager.getString("messages.only_player"));
// /ak admin reset <global/Spieler>
if (args.length >= 3 && args[1].equalsIgnoreCase("reset")) {
String baseTitle = LanguageManager.getString("gui.title");
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];
Player target = Bukkit.getPlayer(targetName);
if (target != null) {
DataHandler.resetPlayer(target);
if (target.getOpenInventory().getTitle().contains(baseTitle)) target.closeInventory();
sender.sendMessage("§a[Admin] §7Daten für §e" + target.getName() + " §7gelöscht.");
} else {
DataHandler.resetOfflinePlayer(targetName);
sender.sendMessage("§a[Admin] §7Offline-Daten für §e" + targetName + " §7gelöscht.");
}
}
return true;
}
Player player = (Player) sender;
sender.sendMessage("§8§m---§r §6Advent Admin §8§m---");
sender.sendMessage("§e/ak admin reload §7- Config neu laden");
sender.sendMessage("§e/ak admin test <1-24/reset> §7- Datum simulieren");
sender.sendMessage("§e/ak admin resetday <global/Spieler> <Tag> §7- Tag löschen");
sender.sendMessage("§e/ak admin reset <global/Spieler> §7- Alles löschen");
return true;
}
if (!(sender instanceof Player player)) return true;
if (!player.hasPermission("adventskalender.use")) {
player.sendMessage(LanguageManager.getString("messages.no_permission"));
player.sendMessage(LanguageManager.getMessage("messages.no_permission"));
return true;
}
AdventInventory.openCalendar(player);
AdventInventory.open(player);
return true;
}
@Override
public List<String> onTabComplete(CommandSender s, Command c, String a, String[] args) {
if (args.length == 1) return Collections.singletonList("admin");
if (args.length == 2 && args[0].equalsIgnoreCase("admin"))
return Arrays.asList("reload", "test", "reset", "resetday");
// Tab-Completion für das Ziel (global oder Spielername)
if (args.length == 3 && (args[1].equalsIgnoreCase("reset") || args[1].equalsIgnoreCase("resetday"))) {
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.length == 3 && args[1].equalsIgnoreCase("test")) || (args.length == 4 && args[1].equalsIgnoreCase("resetday"))) {
List<String> options = new ArrayList<>();
// "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++) {
options.add(String.valueOf(i));
}
String currentInput = (args.length == 3) ? args[2] : args[3];
return options.stream()
.filter(opt -> opt.toLowerCase().startsWith(currentInput.toLowerCase()))
.sorted((s1, s2) -> {
// "reset" immer nach oben sortieren
if (s1.equalsIgnoreCase("reset")) return -1;
if (s2.equalsIgnoreCase("reset")) return 1;
// Restliche Zahlen numerisch vergleichen
return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2));
})
.collect(Collectors.toList());
}
return Collections.emptyList();
}
}

View File

@@ -3,116 +3,152 @@ package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.profile.PlayerProfile;
import org.bukkit.profile.PlayerTextures;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
public class AdventInventory {
public static void openCalendar(org.bukkit.entity.Player player) {
String title = LanguageManager.getString("gui.title");
Inventory inv = Bukkit.createInventory(null, 54, title);
// Textur für das geöffnete Geschenk (Wreath)
private static final String OPEN_WREATH_URL = "http://textures.minecraft.net/texture/5e39248b341c87ce3e4294ac214c6f74468889cb1273ae7412906fd28db097e8";
for (int i = 0; i < 24; i++) {
int day = i + 1;
ItemStack item = getCalendarItem(player, day);
inv.setItem(i, item);
// Texturen für die geschlossenen Tage (1-24)
private static final Map<Integer, String> CLOSED_TEXTURES = new HashMap<>();
private static final Map<String, ItemStack> HEAD_CACHE = new HashMap<>();
static {
// Hier sind die URLs für die "Christmas Calendar #01-24 (red)" Reihe
CLOSED_TEXTURES.put(1, "http://textures.minecraft.net/texture/105b356be68ac56cfe0611b95027adf1ee67050e4cd66a0a99678fc2e25b6b0f");
CLOSED_TEXTURES.put(2, "http://textures.minecraft.net/texture/40ec45e8ffb9cb714d1412bb9ee54fd1e7a3140c02e35ae0d3f308f1666ba126");
CLOSED_TEXTURES.put(3, "http://textures.minecraft.net/texture/2466629a31aae22dba15a7b710a4517308656c19ba39a89ac2de2915a46e2823");
CLOSED_TEXTURES.put(4, "http://textures.minecraft.net/texture/97a1956ed6f32e18b139f2d709355ac6945d0ab8fecd04d4c159d08006bfbe58");
CLOSED_TEXTURES.put(5, "http://textures.minecraft.net/texture/855fad012c8844fc313510436b4919e002d93d6a0761f7bdaf78069683eee5e6");
CLOSED_TEXTURES.put(6, "http://textures.minecraft.net/texture/c0400111ae026101077b59d8482c84a832c74f4f2105fc13b7c94f5b8000ea3");
CLOSED_TEXTURES.put(7, "http://textures.minecraft.net/texture/a34505ed2567ea2efbab49977f7da8f1178db9ae53a0e99737286dc56e63636a");
CLOSED_TEXTURES.put(8, "http://textures.minecraft.net/texture/a17ddf12485fb54cab9c9ffb55feea62a2d90b825772c6ea38561ce750cb6f83");
CLOSED_TEXTURES.put(9, "http://textures.minecraft.net/texture/f75022feb11b736e50bc7d19d26d4ee00cecd6734a2138d2579f799b119b6734");
CLOSED_TEXTURES.put(10, "http://textures.minecraft.net/texture/6b84208e4be2233fcd96e7f02d9613d5437f1d006236277c57d40652c5be2f23");
CLOSED_TEXTURES.put(11, "http://textures.minecraft.net/texture/19aa8a65ba29b4b7cbb9c66cf59fb537ba2c3013a958620b976b9f06248e8dda");
CLOSED_TEXTURES.put(12, "http://textures.minecraft.net/texture/7dc1330b302e800c31c8dfefbd1b996016c34649f3a396749d3e048637364aad");
CLOSED_TEXTURES.put(13, "http://textures.minecraft.net/texture/7e98a3884e0eac967c73a2bcbff7df62ab2fb327b4ceb9e636c74254866b5c7c");
CLOSED_TEXTURES.put(14, "http://textures.minecraft.net/texture/3866643decb2d148999f9768dae89fbdb196b30d55f8846b1fbbe736bb7e5042");
CLOSED_TEXTURES.put(15, "http://textures.minecraft.net/texture/744a95611950cc7f154c9f56ad472c1941376ec171efb8ba476e6cec4f866526");
CLOSED_TEXTURES.put(16, "http://textures.minecraft.net/texture/1546959556eff4b8827ca50d8df686e8f20d5c843da689dddc48bff0a217efbe");
CLOSED_TEXTURES.put(17, "http://textures.minecraft.net/texture/1f4fb00e824c97d9cfba4c44def27a79513978979515048f2e69eb5b7123c159");
CLOSED_TEXTURES.put(18, "http://textures.minecraft.net/texture/9216b42018004a86081b5c1ee87dc8f12770f1859266e2cd359b75b44b5e7680");
CLOSED_TEXTURES.put(19, "http://textures.minecraft.net/texture/93c7097291042c394267892c56feee9ba3936826ea5b2232f46cb5474f9cd937");
CLOSED_TEXTURES.put(20, "http://textures.minecraft.net/texture/4f85a13977d01865a9d95d3279528285872551c36fd5231c7e3ab9897a7560fd");
CLOSED_TEXTURES.put(21, "http://textures.minecraft.net/texture/cdb512403f3610966d10bd3ca9fc61fdc5021268382f69719ac5679f3efd4c14");
CLOSED_TEXTURES.put(22, "http://textures.minecraft.net/texture/cadaef824771250374fc3e82efe020ab765e665340cc2393f3b4ae8d6d18529c");
CLOSED_TEXTURES.put(23, "http://textures.minecraft.net/texture/266f896c37a9d7fb857ff8c9f6f5e3540d17e1b893419f523eb199b71e71dde3");
CLOSED_TEXTURES.put(24, "http://textures.minecraft.net/texture/f081f9274e4ef802453ac95381c00426e462440d3bb085fb3afefa6ded6a8d01");
// Hinweis: In der Praxis füllst du hier alle 1-24 mit den URLs von Minecraft-Heads.
}
ItemStack filler = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
ItemMeta fillerMeta = filler.getItemMeta();
fillerMeta.setDisplayName(" ");
filler.setItemMeta(fillerMeta);
for (int i = 24; i < 54; i++) {
inv.setItem(i, filler);
public static void initCache() {
// Cache den Wreath-Kopf
HEAD_CACHE.put("OPENED", createBaseSkull(OPEN_WREATH_URL));
// Cache alle nummerierten geschlossenen Köpfe
for (int i = 1; i <= 24; i++) {
if (CLOSED_TEXTURES.containsKey(i)) {
HEAD_CACHE.put("CLOSED_" + i, createBaseSkull(CLOSED_TEXTURES.get(i)));
}
}
}
public static void open(Player player) {
String title = " " + LanguageManager.getString("gui.title");
Inventory inv = Bukkit.createInventory(null, 54, title);
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);
for (int i = 0; i < 54; i++) {
if (i < 9 || i >= 45 || i % 9 == 0 || (i + 1) % 9 == 0) {
Material m = (i % 2 == 0) ? Material.RED_STAINED_GLASS_PANE : Material.GREEN_STAINED_GLASS_PANE;
inv.setItem(i, createFiller(m));
} else {
inv.setItem(i, createFiller(Material.WHITE_STAINED_GLASS_PANE));
}
}
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 <= 24; day++) {
boolean claimed = DataHandler.hasClaimed(player, day);
boolean isAvailable = currentDay >= day;
inv.setItem(slots[day - 1], createGiftItem(day, claimed, isAvailable));
}
player.openInventory(inv);
}
public static ItemStack getCalendarItem(org.bukkit.entity.Player player, int day) {
LocalDate today = LocalDate.now();
String prefix = LanguageManager.getString("prefix");
private static ItemStack createGiftItem(int day, boolean claimed, boolean isAvailable) {
ItemStack item;
if (claimed) {
item = HEAD_CACHE.getOrDefault("OPENED", new ItemStack(Material.PLAYER_HEAD)).clone();
} else {
item = HEAD_CACHE.getOrDefault("CLOSED_" + day, new ItemStack(Material.PLAYER_HEAD)).clone();
}
if (today.getMonth() != Month.DECEMBER || today.getDayOfMonth() < day) {
ItemStack item = new ItemStack(Material.BARRIER);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(prefix + "§cTag " + day);
List<String> lore = new ArrayList<>();
lore.add(prefix + "§7Noch nicht verfügbar.");
lore.add(prefix + "§7Komme am " + day + ". Dezember wieder.");
lore.add(" ");
if (claimed) {
meta.setDisplayName(LanguageManager.getString("rewards.day_" + day + ".name"));
meta.addEnchant(Enchantment.LUCK, 1, true);
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"));
}
}
meta.setLore(lore);
item.setItemMeta(meta);
return item;
}
if (CalendarData.hasClaimed(player, day)) {
ItemStack item = new ItemStack(Material.GREEN_STAINED_GLASS_PANE);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(prefix + "§aTag " + day + " - Geöffnet");
List<String> lore = new ArrayList<>();
lore.add(prefix + LanguageManager.getString("messages.day_already_claimed"));
meta.setLore(lore);
private static ItemStack createBaseSkull(String url) {
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) item.getItemMeta();
try {
UUID uuid = UUID.nameUUIDFromBytes(url.getBytes());
PlayerProfile profile = Bukkit.createPlayerProfile(uuid, "AdventHead");
profile.getTextures().setSkin(new URL(url));
meta.setOwnerProfile(profile);
item.setItemMeta(meta);
} catch (MalformedURLException e) { e.printStackTrace(); }
return item;
}
ItemStack item = new ItemStack(Material.CHEST);
private static ItemStack createFiller(Material material) {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(prefix + "§eTag " + day + " - Noch zu holen!");
List<String> lore = new ArrayList<>();
lore.add(prefix + "§7Klicke, um dein Geschenk zu erhalten.");
meta.setLore(lore);
item.setItemMeta(meta);
return item;
}
public static ItemStack getRewardItem(int day) {
String path = "rewards." + day;
if (!Adventskalender.getInstance().getConfig().contains(path)) {
return new ItemStack(Material.AIR);
}
String materialName = Adventskalender.getInstance().getConfig().getString(path + ".material");
int amount = Adventskalender.getInstance().getConfig().getInt(path + ".amount");
String key = Adventskalender.getInstance().getConfig().getString(path + ".key");
Material material = Material.getMaterial(materialName);
if (material == null) {
Adventskalender.getInstance().getLogger().warning("Material '" + materialName + "' für Tag " + day + " nicht gefunden!");
return new ItemStack(Material.BARRIER);
}
ItemStack item = new ItemStack(material, amount);
ItemMeta meta = item.getItemMeta();
String namePath = key + ".name";
String lorePath = key + ".lore";
meta.setDisplayName(LanguageManager.getString(namePath));
meta.setLore(LanguageManager.getStringList(lorePath));
List<Map<?, ?>> enchantmentList = Adventskalender.getInstance().getConfig().getMapList(path + ".enchantments");
if (enchantmentList != null) {
for (Map<?, ?> enchantmentMap : enchantmentList) {
String enchantName = (String) enchantmentMap.get("type");
int level = (int) enchantmentMap.get("level");
Enchantment enchantment = Enchantment.getByName(enchantName);
if (enchantment != null) {
meta.addEnchant(enchantment, level, true);
}
}
}
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
item.setItemMeta(meta);
if (meta != null) { meta.setDisplayName(" "); item.setItemMeta(meta); }
return item;
}
}

View File

@@ -1,56 +1,135 @@
package de.mviper.adventskalender;
import org.bukkit.*;
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.ChatColor;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerJoinEvent;
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;
public class AdventListener implements Listener {
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
String translatedTitle = LanguageManager.getString("gui.title");
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
// Nachricht an Admins senden, wenn ein Update da ist
if (player.hasPermission("adventskalender.admin")) {
// Delay von 2 Sekunden (40 Ticks), damit die Nachricht nicht im Join-Spam untergeht
Bukkit.getScheduler().runTaskLater(Adventskalender.getInstance(), () -> {
if (Adventskalender.getInstance().isUpdateAvailable()) {
String latest = Adventskalender.getInstance().getLatestVersion();
String current = Adventskalender.getInstance().getDescription().getVersion();
player.sendMessage("§7 ");
player.sendMessage("§8§m+-------------------------------------------+");
player.sendMessage("§6§l ADVENTSKALENDER UPDATE");
player.sendMessage("§7 ");
player.sendMessage("§7 Eine neue Version ist verfügbar: §a§l" + latest);
player.sendMessage("§7 Installierte Version: §c" + current);
player.sendMessage("§7 ");
// Erstellung der klickbaren Nachricht
TextComponent message = new TextComponent("§e §nKLICKE HIER ZUM DOWNLOAD");
message.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.spigotmc.org/resources/130974/"));
message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("§7Öffnet die Spigot-Seite")));
player.spigot().sendMessage(message);
player.sendMessage("§7 ");
player.sendMessage("§8§m+-------------------------------------------+");
player.sendMessage("§7 ");
}
}, 40L);
}
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
String baseTitle = LanguageManager.getString("gui.title");
if (!event.getView().getTitle().contains(baseTitle)) return;
if (event.getView().getTitle().equals(translatedTitle)) {
event.setCancelled(true);
if (!(event.getWhoClicked() instanceof Player)) return;
Player player = (Player) event.getWhoClicked();
if (!(event.getWhoClicked() instanceof Player player)) return;
int slot = event.getSlot();
if (slot < 0 || slot > 23) return;
ItemStack clicked = event.getCurrentItem();
if (clicked == null || clicked.getType() != Material.PLAYER_HEAD) return;
if (!clicked.hasItemMeta() || !clicked.getItemMeta().hasDisplayName()) return;
int day = slot + 1;
LocalDate today = LocalDate.now();
String displayName = ChatColor.stripColor(clicked.getItemMeta().getDisplayName());
int day;
try {
day = Integer.parseInt(displayName.replaceAll("[^0-9]", ""));
} catch (NumberFormatException e) {
return;
}
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);
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);
}
}
private void giveReward(Player player, int day) {
String path = "rewards." + day;
String matName = Adventskalender.getInstance().getConfig().getString(path + ".material", "COOKIE");
int amount = Adventskalender.getInstance().getConfig().getInt(path + ".amount", 1);
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 (today.getMonth() == Month.DECEMBER && today.getDayOfMonth() >= day && !CalendarData.hasClaimed(player, day)) {
ItemStack reward = AdventInventory.getRewardItem(day);
if (reward != null && reward.getType() != Material.AIR) {
if (player.getInventory().firstEmpty() == -1) {
player.getWorld().dropItemNaturally(player.getLocation(), reward);
player.sendMessage(LanguageManager.getString("messages.inventory_full"));
player.sendMessage(LanguageManager.getMessage("messages.inventory_full"));
} else {
player.getInventory().addItem(reward);
}
player.sendMessage(LanguageManager.getString("messages.reward_received").replace("%day%", String.valueOf(day)));
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.0f);
player.spawnParticle(Particle.VILLAGER_HAPPY, player.getLocation().add(0, 1, 0), 20, 0.5, 0.5, 0.5, 0);
}
CalendarData.setClaimed(player, day);
ItemStack newItem = AdventInventory.getCalendarItem(player, day);
event.getInventory().setItem(slot, newItem);
} else {
player.sendMessage(LanguageManager.getString("messages.day_not_available"));
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1.0f, 1.0f);
}
}
player.sendMessage(LanguageManager.getMessage("messages.reward_received").replace("%day%", String.valueOf(day)));
}
}

View File

@@ -1,39 +1,62 @@
package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.plugin.java.JavaPlugin;
public final class Adventskalender extends JavaPlugin {
private static Adventskalender instance;
private NamespacedKey dataKey;
// Update-Status Variablen
private boolean updateAvailable = false;
private String latestVersion = "";
@Override
public void onEnable() {
instance = this;
saveDefaultConfig();
this.dataKey = new NamespacedKey(this, "claimed_days_v2");
saveDefaultConfig();
LanguageManager.setup();
CalendarData.setup();
// Initialisierung der Daten (MySQL wird hier bevorzugt behandelt)
DataHandler.setup();
// Textur-Puffer füllen, um Steve-Köpfe zu vermeiden
AdventInventory.initCache();
getCommand("adventskalender").setExecutor(new AdventCommand());
getServer().getPluginManager().registerEvents(new AdventListener(), this);
// Prüfen, ob PlaceholderAPI vorhanden ist und unsere neue Erweiterung registrieren
// Update Checker Integration (ID: 130974)
new UpdateChecker(this, 130974).getVersion(version -> {
if (!this.getDescription().getVersion().equals(version)) {
this.updateAvailable = true;
this.latestVersion = version;
getLogger().warning("Eine neue Version (" + version + ") ist verfügbar!");
getLogger().warning("Download: https://www.spigotmc.org/resources/130974/");
} else {
getLogger().info("Das Plugin ist auf dem neuesten Stand.");
}
});
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
new AdventCalendarExpansion().register();
getLogger().info("PlaceholderAPI expansion registered.");
} else {
getLogger().warning("PlaceholderAPI not found. Placeholders will not work.");
}
getLogger().info("Adventskalender-Plugin wurde erfolgreich aktiviert!");
getLogger().info("Adventskalender erfolgreich gestartet!");
}
@Override
public void onDisable() {
getLogger().info("Adventskalender-Plugin wurde deaktiviert.");
MySQLManager.close();
}
public static Adventskalender getInstance() {
return instance;
}
public static Adventskalender getInstance() { return instance; }
public NamespacedKey getDataKey() { return dataKey; }
// Getter für den Update-Status
public boolean isUpdateAvailable() { return updateAvailable; }
public String getLatestVersion() { return latestVersion; }
}

View File

@@ -1,68 +0,0 @@
package de.mviper.adventskalender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class CalendarData {
private static File file;
private static FileConfiguration customFile;
public static void setup() {
file = new File(Adventskalender.getInstance().getDataFolder(), "calendar.yml");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
Adventskalender.getInstance().getLogger().severe("Konnte calendar.yml nicht erstellen: " + e.getMessage());
}
}
customFile = YamlConfiguration.loadConfiguration(file);
}
public static void save() {
try {
customFile.save(file);
} catch (IOException e) {
Adventskalender.getInstance().getLogger().severe("Konnte calendar.yml nicht speichern: " + e.getMessage());
}
}
public static boolean hasClaimed(Player player, int day) {
boolean isGlobal = Adventskalender.getInstance().getConfig().getBoolean("calendar.use_global_calendar");
if (isGlobal) {
List<Integer> claimedDays = customFile.getIntegerList("global_claimed_days");
return claimedDays.contains(day);
} else {
List<Integer> claimedDays = customFile.getIntegerList(player.getUniqueId().toString());
return claimedDays.contains(day);
}
}
public static void setClaimed(Player player, int day) {
boolean isGlobal = Adventskalender.getInstance().getConfig().getBoolean("calendar.use_global_calendar");
if (isGlobal) {
List<Integer> claimedDays = customFile.getIntegerList("global_claimed_days");
claimedDays.add(day);
customFile.set("global_claimed_days", claimedDays);
} else {
List<Integer> claimedDays = customFile.getIntegerList(player.getUniqueId().toString());
claimedDays.add(day);
customFile.set(player.getUniqueId().toString(), claimedDays);
}
save();
}
public static int getClaimedCount(Player player) {
if (Adventskalender.getInstance().getConfig().getBoolean("calendar.use_global_calendar")) {
return customFile.getIntegerList("global_claimed_days").size();
} else {
return customFile.getIntegerList(player.getUniqueId().toString()).size();
}
}
}

View File

@@ -0,0 +1,241 @@
package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
public class DataHandler {
private static File file;
private static FileConfiguration dataConfig;
private static boolean mysqlActive = false;
public static void setup() {
if (Adventskalender.getInstance().getConfig().getBoolean("mysql.enable")) {
MySQLManager.connect();
if (MySQLManager.isConnected()) {
mysqlActive = true;
Adventskalender.getInstance().getLogger().info("MySQL-Verbindung steht. data.yml wird deaktiviert.");
return;
} else {
Adventskalender.getInstance().getLogger().warning("MySQL aktiviert, aber Verbindung fehlgeschlagen! Nutze data.yml als Backup.");
}
}
if (!Adventskalender.getInstance().getDataFolder().exists()) {
Adventskalender.getInstance().getDataFolder().mkdirs();
}
file = new File(Adventskalender.getInstance().getDataFolder(), "data.yml");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
dataConfig = YamlConfiguration.loadConfiguration(file);
}
public static boolean hasClaimed(Player player, int day) {
if (isGlobal()) {
return GlobalDataManager.isDayClaimedGlobally(day);
}
if (mysqlActive && MySQLManager.isConnected()) {
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement("SELECT 1 FROM advent_players WHERE uuid = ? AND day = ?")) {
ps.setString(1, player.getUniqueId().toString());
ps.setInt(2, day);
return ps.executeQuery().next();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (dataConfig != null) {
List<Integer> claimed = dataConfig.getIntegerList("players." + player.getUniqueId() + ".claimed");
return claimed.contains(day);
}
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")
OfflinePlayer target = Bukkit.getOfflinePlayer(playerName);
try (PreparedStatement ps = MySQLManager.getConnection().prepareStatement("DELETE FROM advent_players WHERE uuid = ? AND day = ?")) {
ps.setString(1, 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) {
if (mysqlActive && MySQLManager.isConnected()) {
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 {
dataConfig.save(file);
} catch (IOException e) {
e.printStackTrace();
}
}
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();
}
}
}
}

View File

@@ -1,41 +1,35 @@
package de.mviper.adventskalender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
public class LanguageManager {
private static FileConfiguration languageConfig;
private static YamlConfiguration cfg;
private static String prefix;
public static void setup() {
String lang = Adventskalender.getInstance().getConfig().getString("general.language", "de");
File languageFile = new File(Adventskalender.getInstance().getDataFolder(), "messages_" + lang + ".yml");
if (!languageFile.exists()) {
Adventskalender.getInstance().saveResource("messages_" + lang + ".yml", false);
}
languageConfig = YamlConfiguration.loadConfiguration(languageFile);
File f = new File(Adventskalender.getInstance().getDataFolder(), "messages_" + lang + ".yml");
if (!f.exists()) Adventskalender.getInstance().saveResource("messages_" + lang + ".yml", false);
cfg = YamlConfiguration.loadConfiguration(f);
prefix = ChatColor.translateAlternateColorCodes('&', cfg.getString("prefix", "&6[Advent] "));
}
public static String getString(String path) {
if (languageConfig.contains(path)) {
return format(languageConfig.getString(path));
String s = cfg.getString(path);
return s == null ? "§cKey error: " + path : ChatColor.translateAlternateColorCodes('&', s);
}
return "§cLanguage key not found: " + path;
public static String getMessage(String path) {
return prefix + getString(path);
}
public static List<String> getStringList(String path) {
if (languageConfig.contains(path)) {
return languageConfig.getStringList(path).stream().map(LanguageManager::format).toList();
}
return List.of("§cLanguage key list not found: " + path);
}
private static String format(String message) {
return org.bukkit.ChatColor.translateAlternateColorCodes('&', message);
return cfg.getStringList(path).stream()
.map(s -> ChatColor.translateAlternateColorCodes('&', s))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,66 @@
package de.mviper.adventskalender;
import org.bukkit.configuration.file.FileConfiguration;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class MySQLManager {
private static Connection connection;
public static void connect() {
FileConfiguration config = Adventskalender.getInstance().getConfig();
String host = config.getString("mysql.host");
int port = config.getInt("mysql.port");
String database = config.getString("mysql.database");
String user = config.getString("mysql.user");
String password = config.getString("mysql.password");
try {
if (connection != null && !connection.isClosed()) return;
synchronized (MySQLManager.class) {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database + "?autoReconnect=true&useSSL=false", user, password);
}
createTables();
Adventskalender.getInstance().getLogger().info("MySQL-Verbindung erfolgreich hergestellt!");
} catch (Exception e) {
Adventskalender.getInstance().getLogger().severe("MySQL-Verbindung fehlgeschlagen: " + e.getMessage());
}
}
private static void createTables() {
try (Statement s = connection.createStatement()) {
// Tabelle für Einzelspieler-Daten
s.executeUpdate("CREATE TABLE IF NOT EXISTS advent_players (uuid VARCHAR(36), day INT, PRIMARY KEY(uuid, day))");
// Tabelle für globalen Modus
s.executeUpdate("CREATE TABLE IF NOT EXISTS advent_global (day INT PRIMARY KEY, player_name VARCHAR(16))");
} catch (SQLException e) {
e.printStackTrace();
}
}
public static boolean isConnected() {
try {
return connection != null && !connection.isClosed();
} catch (SQLException e) {
return false;
}
}
public static void close() {
try {
if (isConnected()) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
return connection;
}
}

View File

@@ -1,75 +0,0 @@
package de.mviper.adventskalender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Verwaltet die Spielerdaten in einer separaten YAML-Datei.
* Speichert, welcher Spieler an welchem Tag sein Geschenk bereits geholt hat.
*/
public class PlayerData {
private static File file;
private static FileConfiguration customFile;
/**
* Erstellt die playerdata.yml-Datei, falls sie nicht existiert.
*/
public static void setup() {
file = new File(Adventskalender.getInstance().getDataFolder(), "playerdata.yml");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
Adventskalender.getInstance().getLogger().severe("Konnte playerdata.yml nicht erstellen: " + e.getMessage());
}
}
customFile = YamlConfiguration.loadConfiguration(file);
}
public static FileConfiguration get() {
return customFile;
}
public static void save() {
try {
customFile.save(file);
} catch (IOException e) {
Adventskalender.getInstance().getLogger().severe("Konnte playerdata.yml nicht speichern: " + e.getMessage());
}
}
public static void reload() {
customFile = YamlConfiguration.loadConfiguration(file);
}
/**
* Prüft, ob ein Spieler ein Geschenk für einen bestimmten Tag bereits geholt hat.
* @param uuid Die UUID des Spielers
* @param day Der Tag (1-24)
* @return true, wenn bereits geholt, sonst false
*/
public static boolean hasClaimed(UUID uuid, int day) {
// Wir speichern eine Liste der geöffneten Tage für jeden Spieler
List<Integer> claimedDays = customFile.getIntegerList(uuid.toString());
return claimedDays.contains(day);
}
/**
* Markiert einen Tag für einen Spieler als geholt.
* @param uuid Die UUID des Spielers
* @param day Der Tag (1-24)
*/
public static void setClaimed(UUID uuid, int day) {
List<Integer> claimedDays = customFile.getIntegerList(uuid.toString());
claimedDays.add(day);
customFile.set(uuid.toString(), claimedDays);
save();
}
}

View File

@@ -0,0 +1,33 @@
package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Scanner;
import java.util.function.Consumer;
public class UpdateChecker {
private final JavaPlugin plugin;
private final int resourceId;
public UpdateChecker(JavaPlugin plugin, int resourceId) {
this.plugin = plugin;
this.resourceId = resourceId;
}
public void getVersion(final Consumer<String> consumer) {
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
try (InputStream is = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId).openStream();
Scanner scann = new Scanner(is)) {
if (scann.hasNext()) {
consumer.accept(scann.next());
}
} catch (IOException e) {
plugin.getLogger().info("Update-Check fehlgeschlagen: " + e.getMessage());
}
});
}
}

View File

@@ -8,6 +8,9 @@ general:
# Verfügbare Optionen: 'de', 'en'
language: "de"
# Nur für Testzwecke: Simuliert einen Tag (0 = echtes Datum verwenden)
test_day: 0
# --- Kalender-Einstellungen ---
calendar:
# Soll jeder Spieler seinen eigenen, individuellen Kalender haben?
@@ -19,6 +22,15 @@ calendar:
# Dieser Text ist ein Schlüssel, der in der messages.yml übersetzt wird.
gui_title_key: "adventskalender.gui_title"
# Datenbank für Bungeecord Nutzung
mysql:
enable: false
host: "localhost"
port: 3306
database: "adventskalender"
user: "root"
password: ""
# --- Belohnungen für die Tage 1 bis 24 ---
# Hier definierst du die Items für jeden Tag.
# 'material': Der Materialname des Items (z.B. DIAMOND, ELYTRA).

View File

@@ -1,15 +1,14 @@
# =============================================================================
# Adventskalender Plugin - Deutsche Sprachdatei
# Adventskalender Plugin - Deutsche Sprachdatei (v2.0 - 2026)
# =============================================================================
# --- Prefix ---
# Dieser Prefix wird vor fast jeder Nachricht im Chat angezeigt.
prefix: "&6[Adventskalender] &r"
# --- GUI-Texte ---
gui:
# Der Titel des Adventskalender-Inventars.
title: "&6✦ Adventskalender 2024 ✦"
title: "&6✦ Adventskalender 2026 ✦"
# --- Nachrichten an Spieler ---
# Verfügbare Platzhalter:
@@ -18,90 +17,92 @@ gui:
messages:
no_permission: "&cDafür hast du keine Berechtigung."
only_player: "&cDieser Befehl kann nur von einem Spieler ausgeführt werden."
inventory_full: "&cDein Inventar war voll! Das Item wurde auf den Boden geworfen."
reward_received: "&aDu hast dein Geschenk für Tag %day% erhalten!"
day_not_available: "&cDieses Geschenk kannst du (noch) nicht öffnen."
day_already_claimed: "&7Du hast dein Geschenk für diesen Tag bereits geholt."
inventory_full: "&cDein Inventar ist voll! Dein Geschenk wurde vor deine Füße geworfen."
reward_received: "&aGlückwunsch! Du hast dein Geschenk für Tag &e%day% &aerhalten!"
day_not_available: "&cDieses Türchen ist noch fest verschlossen. Gedulde dich noch etwas!"
day_already_claimed: "&7Du hast dieses Geschenk bereits abgeholt."
global_already_claimed: "&cDieses Geschenk wurde heute bereits von jemand anderem abgeholt!"
# --- Admin-Nachrichten ---
admin:
reload_success: "&aKonfiguration und Sprachdateien wurden 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_off: "&e[Admin] &7Test-Modus deaktiviert. Es wird wieder das reale Datum genutzt."
player_not_found: "&cDer Spieler '%player%' wurde nicht gefunden."
invalid_day: "&cUngültiger Tag. Bitte wähle eine Zahl zwischen 1 und 24."
open_success: "&aDu hast für %player% das Türchen für Tag %day% geöffnet."
# --- Namen und Beschreibungen für die Belohnungs-Items ---
# Die Schlüssel hier (z.B. 'day_1') müssen mit den 'keys' in der config.yml übereinstimmen.
# --- Belohnungs-Texte im GUI ---
# Diese Schlüssel werden vom AdventInventory genutzt, um die Items im Menü zu benennen.
rewards:
day_1:
name: "&bErste Diamanten!"
name: "&bTag 1: Erste Diamanten!"
lore: ["&7Ein kleiner Vorgeschmack auf den Reichtum...", "&7Viel Spaß damit!"]
day_2:
name: "&6Goldene Äpfel"
name: "&6Tag 2: Goldene Äpfel"
lore: ["&7Für den Fall, dass es mal eng wird..."]
day_3:
name: "&cSchwert des Helden"
name: "&cTag 3: Schwert des Helden"
lore: ["&7Ein mächtiges Schwert, um Monster zu jagen."]
day_4:
name: "&aFlügel der Freiheit"
name: "&aTag 4: Flügel der Freiheit"
lore: ["&7Sei frei wie ein Adler!"]
day_5:
name: "&8Ein Hauch von Netherit"
name: "&8Tag 5: Ein Hauch von Netherit"
lore: ["&7Sehr wertvoll und selten."]
day_6:
name: "&6Göttlicher Apfel"
name: "&6Tag 6: Göttlicher Apfel"
lore: ["&7Ein Geschenk der Götter... oder des Admins."]
day_7:
name: "&eBergbau-Meister"
name: "&eTag 7: Bergbau-Meister"
lore: ["&7Für die größten Schätze der Welt."]
day_8:
name: "&dTotem des Unvergänglichen"
name: "&dTag 8: Totem des Unvergänglichen"
lore: ["&7Ein kleines Extra-Leben."]
day_9:
name: "&5Stern des Nethers"
name: "&5Tag 9: Stern des Nethers"
lore: ["&7Der Kern eines mächtigen Wesens."]
day_10:
name: "&9Magische Truhe"
name: "&9Tag 10: Magische Truhen"
lore: ["&7Hält all deine Schätze sicher."]
day_11:
name: "&3Stachel des Ozeans"
name: "&3Tag 11: Stachel des Ozeans"
lore: ["&7Die Waffe der Wächter."]
day_12:
name: "&7Helm der Weisheit"
name: "&7Tag 12: Helm der Weisheit"
lore: ["&7Schützt deinen Kopf."]
day_13:
name: "&fSattel für ein treues Tier"
name: "&fTag 13: Sattel"
lore: ["&7Für dein nächstes Abenteuer zu Pferd."]
day_14:
name: "&eNamensschilder"
name: "&eTag 14: Namensschilder"
lore: ["&7Gib deinem Lieblings-Wolf einen Namen!"]
day_15:
name: "&aSchallplatte - cat"
name: "&aTag 15: Schallplatte - cat"
lore: ["&7Relaxende Musik für eine gemütliche Zeit."]
day_16:
name: "&5Endkristall"
name: "&5Tag 16: Endkristalle"
lore: ["&7Mächtig und gefährlich. Mit Vorsicht genießen!"]
day_17:
name: "&7Brustpanzer der Unzerstörbarkeit"
name: "&7Tag 17: Brustpanzer der Unzerstörbarkeit"
lore: ["&7Bietet maximalen Schutz."]
day_18:
name: "&bHerz des Ozeans"
name: "&bTag 18: Herz des Ozeans"
lore: ["&7Das Zentrum einer Conduit-Struktur."]
day_19:
name: "&fNautilusmuschel"
name: "&fTag 19: Nautilusmuscheln"
lore: ["&7Ein seltenes Gut aus den Tiefen."]
day_20:
name: "&8Kopf des Enderdrachen"
name: "&8Tag 20: Kopf des Enderdrachen"
lore: ["&7Eine beeindruckende Trophäe."]
day_21:
name: "&8Stiefel der Tiefe"
name: "&8Tag 21: Stiefel der Tiefe"
lore: ["&7Lass dich nicht im Feuer verbrennen."]
day_22:
name: "&dVerzauberungstisch"
name: "&dTag 22: Verzauberungstisch"
lore: ["&7Kreiere deine eigenen magischen Items."]
day_23:
name: "&bDie ultimative Hacke"
name: "&bTag 23: Die ultimative Hacke"
lore: ["&7Nicht nur für den Ackerbau..."]
day_24:
name: "&cDas große Finale! - Feuerwerk"
name: "&cTag 24: Das große Finale!"
lore: ["&7Frohe Weihnachten!", "&7Feiere mit einem riesigen Feuerwerk!"]

View File

@@ -1,15 +1,14 @@
# =============================================================================
# Adventskalender Plugin - English Language File
# Adventskalender Plugin - English Language File (v2.0 - 2026)
# =============================================================================
# --- Prefix ---
# This prefix will be shown before most chat messages.
prefix: "&6[Adventskalender] &r"
prefix: "&6[Advent] &r"
# --- GUI Texts ---
gui:
# The title of the Advent Calendar inventory.
title: "&6✦ Advent Calendar 2024 ✦"
title: "&6✦ Advent Calendar 2026 ✦"
# --- Messages to Players ---
# Available placeholders:
@@ -18,90 +17,91 @@ gui:
messages:
no_permission: "&cYou don't have permission for that."
only_player: "&cThis command can only be executed by a player."
inventory_full: "&cYour inventory was full! The item was dropped on the ground."
reward_received: "&aYou have received your gift for day %day%!"
day_not_available: "&cYou cannot open this gift (yet)."
day_already_claimed: "&7You have already claimed your gift for this day."
inventory_full: "&cYour inventory is full! Your gift was dropped at your feet."
reward_received: "&aCongratulations! You have received your gift for day &e%day%&a!"
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."
global_already_claimed: "&cThis gift has already been claimed by someone else today!"
# --- Admin Messages ---
admin:
reload_success: "&aConfiguration and language files have been reloaded."
test_mode_on: "&e[Admin] &7Test mode set to day &6%day%&7."
test_mode_off: "&e[Admin] &7Test mode disabled. Using real-time date again."
player_not_found: "&cThe player '%player%' was not found."
invalid_day: "&cInvalid day. Please choose a number between 1 and 24."
open_success: "&aYou have opened day %day% for %player%."
# --- Names and Descriptions for Reward Items ---
# The keys here (e.g., 'day_1') must match the 'keys' in the config.yml.
# --- Reward Texts in GUI ---
rewards:
day_1:
name: "&bFirst Diamonds!"
name: "&bDay 1: First Diamonds!"
lore: ["&7A little taste of wealth...", "&7Have fun with it!"]
day_2:
name: "&6Golden Apples"
name: "&6Day 2: Golden Apples"
lore: ["&7In case things get tight..."]
day_3:
name: "&cSword of the Hero"
name: "&cDay 3: Sword of the Hero"
lore: ["&7A mighty sword to hunt monsters."]
day_4:
name: "&aWings of Freedom"
name: "&aDay 4: Wings of Freedom"
lore: ["&7Be free as an eagle!"]
day_5:
name: "&8A Touch of Netherite"
name: "&8Day 5: A Touch of Netherite"
lore: ["&7Very valuable and rare."]
day_6:
name: "&6Godly Apple"
name: "&6Day 6: Godly Apple"
lore: ["&7A gift from the gods... or the admin."]
day_7:
name: "&eMaster Pickaxe"
name: "&eDay 7: Master Pickaxe"
lore: ["&7For the greatest treasures in the world."]
day_8:
name: "&dTotem of Undying"
name: "&dDay 8: Totem of Undying"
lore: ["&7An extra life."]
day_9:
name: "&5Nether Star"
name: "&5Day 9: Nether Star"
lore: ["&7The core of a powerful being."]
day_10:
name: "&9Magic Box"
name: "&9Day 10: Magic Chests"
lore: ["&7Keeps all your treasures safe."]
day_11:
name: "&3Spear of the Ocean"
name: "&3Day 11: Spear of the Ocean"
lore: ["&7The weapon of the guardians."]
day_12:
name: "&7Helmet of Wisdom"
name: "&7Day 12: Helmet of Wisdom"
lore: ["&7Protects your head."]
day_13:
name: "&fSaddle for a Loyal Steed"
name: "&fDay 13: Saddle"
lore: ["&7For your next equestrian adventure."]
day_14:
name: "&eName Tags"
name: "&eDay 14: Name Tags"
lore: ["&7Give your favorite wolf a name!"]
day_15:
name: "&aMusic Disc - cat"
name: "&aDay 15: Music Disc - cat"
lore: ["&7Relaxing music for a cozy time."]
day_16:
name: "&5End Crystal"
name: "&5Day 16: End Crystals"
lore: ["&7Powerful and dangerous. Handle with care!"]
day_17:
name: "&7Chestplate of Durability"
name: "&7Day 17: Chestplate of Durability"
lore: ["&7Offers maximum protection."]
day_18:
name: "&bHeart of the Sea"
name: "&bDay 18: Heart of the Sea"
lore: ["&7The center of a Conduit structure."]
day_19:
name: "&fNautilus Shell"
name: "&fDay 19: Nautilus Shells"
lore: ["&7A rare goodie from the depths."]
day_20:
name: "&8Head of the Ender Dragon"
name: "&8Day 20: Head of the Ender Dragon"
lore: ["&7An impressive trophy."]
day_21:
name: "&8Boots of the Deep"
name: "&8Day 21: Boots of the Deep"
lore: ["&7Don't let yourself get burned in fire."]
day_22:
name: "&dEnchanting Table"
name: "&dDay 22: Enchanting Table"
lore: ["&7Create your own magical items."]
day_23:
name: "&bThe Ultimate Hoe"
name: "&bDay 23: The Ultimate Hoe"
lore: ["&7Not just for farming..."]
day_24:
name: "&cThe Grand Finale! - Fireworks"
name: "&cDay 24: The Grand Finale!"
lore: ["&7Merry Christmas!", "&7Celebrate with a huge firework!"]

View File

@@ -1,9 +1,9 @@
name: Adventskalender
version: '1.0.0'
version: '1.0.1'
main: de.mviper.adventskalender.Adventskalender
api-version: 1.20
author: M_Viper
description: Ein konfigurierbarer Adventskalender für Minecraft.
description: Ein moderner, performanter Adventskalender (2026 Edition).
commands:
adventskalender: