Upload via GUI (19 Dateien)

This commit is contained in:
2026-11-03 19:10:19 +00:00
parent 420b6fcc8e
commit 208ec12e0c
19 changed files with 3587 additions and 1109 deletions
@@ -1,69 +1,138 @@
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 @NotNull String getIdentifier() {
return "ak";
}
@Override
public @NotNull String getAuthor() {
return "M_Viper";
}
@Override
public @NotNull String getVersion() {
return "2.0.0";
}
@Override
public boolean persist() {
return true; // Expansion bleibt nach PAPI-Reload registriert
}
@Override
public String onPlaceholderRequest(Player player, String identifier) {
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";
}
return null; // Placeholder unbekannt
}
}
package de.mviper.adventskalender;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
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 {
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
public @NotNull String getIdentifier() {
return "ak";
}
@Override
public @NotNull String getAuthor() {
return "M_Viper";
}
@Override
public @NotNull String getVersion() {
return Adventskalender.getInstance().getDescription().getVersion();
}
@Override
public boolean persist() {
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
public String onPlaceholderRequest(Player player, String identifier) {
if (player == null) return "";
String id = 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
case "claimed":
return String.valueOf(claimedDays(player, ev).size());
// %ak_day% - Der aktuelle Tag des Kalenders
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 claimedDays(player, ev).contains(currentDay) ? "Ja" : "Nein";
// %ak_remaining% - Wie viele Türchen sind noch offen?
case "remaining":
return String.valueOf(ev.getDays() - claimedDays(player, ev).size());
// %ak_is_global% - Zeigt an, ob der globale Modus aktiv ist
case "is_global":
return ev.isGlobal() ? "Global" : "Spieler-basiert";
}
return null; // Placeholder unbekannt
}
}
@@ -1,160 +1,376 @@
package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.stream.Collectors;
public class AdventCommand implements CommandExecutor, TabCompleter {
@Override
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.getMessage("messages.no_permission"));
return true;
}
// /ak admin reload
if (args.length == 2 && args[1].equalsIgnoreCase("reload")) {
Adventskalender.getInstance().reloadConfig();
LanguageManager.setup();
DataHandler.setup();
sender.sendMessage(LanguageManager.getMessage("messages.admin.reload_success"));
return true;
}
// /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[2]);
if (day < 1 || day > 24) {
sender.sendMessage("§cBitte gib einen Tag zwischen 1 und 24 an (oder 'reset').");
return true;
}
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("§cBitte gib eine gültige Zahl (1-24) oder 'reset' an.");
}
return true;
}
// /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;
}
// /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;
}
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.getMessage("messages.no_permission"));
return true;
}
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();
}
}
package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.time.MonthDay;
import java.util.*;
import java.util.stream.Collectors;
public class AdventCommand implements CommandExecutor, TabCompleter {
private static final int MAX_DAYS = 36;
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
Adventskalender plugin = Adventskalender.getInstance();
if (args.length > 0 && args[0].equalsIgnoreCase("admin")) {
if (!sender.hasPermission("adventskalender.admin")) {
sender.sendMessage(LanguageManager.getMessage("messages.no_permission"));
return true;
}
// /ak admin reload
if (args.length == 2 && args[1].equalsIgnoreCase("reload")) {
plugin.reloadConfig();
plugin.reloadSettings();
LanguageManager.setup();
DataHandler.setup();
AdventInventory.initCache();
sender.sendMessage(LanguageManager.getMessage("messages.admin.reload_success"));
return true;
}
// /ak admin test [Event] <Tag/reset>
if ((args.length == 3 || args.length == 4) && args[1].equalsIgnoreCase("test")) {
CalendarEvent ev = null;
if (args.length == 4) {
ev = EventManager.getAny(args[2]);
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;
}
int day = parseDay(value, ev != null ? ev.getDays() : MAX_DAYS);
if (day == -1) {
sender.sendMessage(LanguageManager.getString("messages.admin.invalid_day"));
return true;
}
for (CalendarEvent t : targets) t.setTestDay(day);
sender.sendMessage(LanguageManager.getString("messages.admin.test_mode_on")
.replace("%day%", String.valueOf(day)).replace("%event%", label(ev)));
return true;
}
// /ak admin resetday [Event] <global/Spieler> <Tag>
if ((args.length == 4 || args.length == 5) && args[1].equalsIgnoreCase("resetday")) {
CalendarEvent ev = null;
int shift = 0;
if (args.length == 5) {
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")) {
DataHandler.resetGlobalDay(event, day);
reply(sender, LanguageManager.getString("messages.admin.reset_day_global")
.replace("%day%", String.valueOf(day)).replace("%event%", label(event)));
} else {
boolean found = DataHandler.resetPlayerDay(target, event, day);
reply(sender, found
? LanguageManager.getString("messages.admin.reset_day_player")
.replace("%day%", String.valueOf(day)).replace("%player%", target).replace("%event%", label(event))
: LanguageManager.getString("messages.admin.player_not_found").replace("%player%", target));
}
});
return true;
}
// /ak admin reset [Event] <global/Spieler>
if (args.length >= 3 && args[1].equalsIgnoreCase("reset")) {
CalendarEvent ev = null;
String targetName = args[2];
if (args.length >= 4) {
ev = EventManager.getAny(args[2]);
if (ev == null) {
sendUnknownEvent(sender, args[2]);
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 {
Player target = Bukkit.getPlayerExact(name);
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;
}
// /ak admin birthday <set|remove|reset> <Spieler> [TT.MM]
if (args.length >= 4 && args[1].equalsIgnoreCase("birthday")) {
String action = args[2].toLowerCase();
String targetName = args[3];
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;
}
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")) {
player.sendMessage(LanguageManager.getMessage("messages.no_permission"));
return true;
}
// /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;
}
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
public List<String> onTabComplete(CommandSender s, Command c, String a, String[] args) {
boolean admin = s.hasPermission("adventskalender.admin");
if (args.length == 1) {
List<String> options = new ArrayList<>(EventManager.all().stream().map(CalendarEvent::getId).toList());
if (BirthdayManager.isEnabled() && s.hasPermission("adventskalender.birthday")) options.add("birthday");
if (admin) options.add("admin");
return filter(options, args[0]);
}
if (args[0].equalsIgnoreCase("birthday")) {
if (args.length == 2) return filter(Arrays.asList("set", "claim", "remove"), args[1]);
return Collections.emptyList();
}
if (!admin || !args[0].equalsIgnoreCase("admin")) return Collections.emptyList();
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 sub = args[1].toLowerCase();
String last = args[args.length - 1];
CalendarEvent ev = EventManager.getAny(args[2]);
switch (sub) {
case "test": {
if (args.length == 3) {
List<String> options = new ArrayList<>(eventIds());
options.add(0, "reset");
options.addAll(dayOptions(MAX_DAYS));
return filter(options, last);
}
if (args.length == 4 && ev != null) {
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();
}
}
@@ -1,154 +1,227 @@
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.*;
public class AdventInventory {
// Textur für das geöffnete Geschenk (Wreath)
private static final String OPEN_WREATH_URL = "http://textures.minecraft.net/texture/5e39248b341c87ce3e4294ac214c6f74468889cb1273ae7412906fd28db097e8";
// 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.
}
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);
}
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();
}
ItemMeta meta = item.getItemMeta();
List<String> lore = new ArrayList<>();
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;
}
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;
}
private static ItemStack createFiller(Material material) {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta != null) { meta.setDisplayName(" "); item.setItemMeta(meta); }
return item;
}
}
package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
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.URI;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class AdventInventory {
// Textur für das geöffnete Geschenk (Wreath)
private static final String OPEN_WREATH_URL = "http://textures.minecraft.net/texture/5e39248b341c87ce3e4294ac214c6f74468889cb1273ae7412906fd28db097e8";
// 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.
}
// Spieler, die gerade einen Kalender geöffnet haben
private static final Set<UUID> VIEWERS = ConcurrentHashMap.newKeySet();
/** 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++) {
if (CLOSED_TEXTURES.containsKey(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 markClosed(Player player) { VIEWERS.remove(player.getUniqueId()); }
public static void closeFor(Player player) {
if (VIEWERS.contains(player.getUniqueId())) player.closeInventory();
}
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++) {
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;
inv.setItem(i, createFiller(m));
} else {
inv.setItem(i, createFiller(Material.WHITE_STAINED_GLASS_PANE));
}
}
for (int day = 1; day <= event.getDays(); day++) {
inv.setItem(event.slotForDay(day), createGiftItem(event, day, claimedDays.contains(day), currentDay >= day));
}
player.openInventory(inv);
// Nach openInventory, da ein vorher offenes Kalender-Inventar dabei geschlossen wird
VIEWERS.add(player.getUniqueId());
}
private static ItemStack baseItem(CalendarEvent event, int day, boolean claimed) {
if (event.isAdvent()) {
return claimed
? HEAD_CACHE.getOrDefault("OPENED", new ItemStack(Material.PLAYER_HEAD)).clone()
: 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();
List<String> lore = new ArrayList<>();
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) {
meta.setDisplayName(rewardName);
// 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);
}
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);
item.setItemMeta(meta);
return item;
}
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(StandardCharsets.UTF_8));
PlayerProfile profile = Bukkit.createPlayerProfile(uuid, "AdventHead");
PlayerTextures textures = profile.getTextures();
textures.setSkin(URI.create(url).toURL());
profile.setTextures(textures);
meta.setOwnerProfile(profile);
item.setItemMeta(meta);
} catch (Exception e) {
Adventskalender.getInstance().getLogger().warning("Kopf-Textur konnte nicht geladen werden (" + url + "): " + e.getMessage());
}
return item;
}
private static ItemStack createFiller(Material material) {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta != null) { meta.setDisplayName(" "); item.setItemMeta(meta); }
return item;
}
}
@@ -1,135 +1,163 @@
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.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 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;
event.setCancelled(true);
if (!(event.getWhoClicked() instanceof Player player)) return;
ItemStack clicked = event.getCurrentItem();
if (clicked == null || clicked.getType() != Material.PLAYER_HEAD) return;
if (!clicked.hasItemMeta() || !clicked.getItemMeta().hasDisplayName()) return;
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 (player.getInventory().firstEmpty() == -1) {
player.getWorld().dropItemNaturally(player.getLocation(), reward);
player.sendMessage(LanguageManager.getMessage("messages.inventory_full"));
} else {
player.getInventory().addItem(reward);
}
player.sendMessage(LanguageManager.getMessage("messages.reward_received").replace("%day%", String.valueOf(day)));
}
}
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.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
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.inventory.ItemStack;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class AdventListener implements Listener {
// Spieler, deren Klick gerade verarbeitet wird (verhindert Spam-Klicks)
private final Set<UUID> busy = ConcurrentHashMap.newKeySet();
@EventHandler
public void onJoin(PlayerJoinEvent event) {
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
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 (!player.isOnline()) return;
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 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
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getInventory().getHolder() instanceof AdventInventory.AdventHolder)) return;
event.setCancelled(true);
if (!(event.getWhoClicked() instanceof Player player)) return;
if (event.getClickedInventory() != event.getInventory()) return;
CalendarEvent calendar = ((AdventInventory.AdventHolder) event.getInventory().getHolder()).getEvent();
int day = calendar.dayForSlot(event.getRawSlot());
if (day == -1) return;
Adventskalender plugin = Adventskalender.getInstance();
if (calendar.currentDay() < day) {
player.sendMessage(LanguageManager.getMessage("messages.day_not_available", calendar));
player.playSound(player.getLocation(), Sound.BLOCK_CHEST_LOCKED, 1f, 1f);
return;
}
// Belohnung vorab prüfen: Bei Konfigurationsfehlern wird der Tag nicht verbraucht
ItemStack reward = buildReward(calendar, day);
if (reward == null) {
player.sendMessage(LanguageManager.getMessage("messages.reward_error", calendar).replace("%day%", String.valueOf(day)));
return;
}
UUID id = player.getUniqueId();
if (!busy.add(id)) return;
// 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 {
DataHandler.resetPlayerDay(player.getName(), calendar, 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)));
}
}
@@ -1,62 +1,103 @@
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;
this.dataKey = new NamespacedKey(this, "claimed_days_v2");
saveDefaultConfig();
LanguageManager.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);
// 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("Adventskalender erfolgreich gestartet!");
}
@Override
public void onDisable() {
MySQLManager.close();
}
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; }
}
package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.plugin.java.JavaPlugin;
import java.time.DateTimeException;
import java.time.ZoneId;
public final class Adventskalender extends JavaPlugin {
private static Adventskalender instance;
private NamespacedKey dataKey;
// Update-Status Variablen
private volatile boolean updateAvailable = false;
private volatile String latestVersion = "";
@Override
public void onEnable() {
instance = this;
this.dataKey = new NamespacedKey(this, "claimed_days_v2");
saveDefaultConfig();
reloadSettings();
LanguageManager.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);
// Update Checker Integration (ID: 130974)
new UpdateChecker(this, 130974).getVersion(version -> {
String current = this.getDescription().getVersion();
if (isNewer(version, current)) {
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("Adventskalender erfolgreich gestartet!");
}
@Override
public void onDisable() {
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 NamespacedKey getDataKey() { return dataKey; }
// Getter für den Update-Status
public boolean isUpdateAvailable() { return updateAvailable; }
public String getLatestVersion() { return latestVersion; }
}
@@ -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; }
}
@@ -1,241 +1,478 @@
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();
}
}
}
}
package de.mviper.adventskalender;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.ConfigurationSection;
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.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 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 FileConfiguration dataConfig;
private static boolean mysqlActive = false;
public static synchronized void setup() {
mysqlActive = false;
dataConfig = null;
file = null;
MySQLManager.close();
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) {
Adventskalender.getInstance().getLogger().severe("data.yml konnte nicht erstellt werden: " + e.getMessage());
}
}
dataConfig = YamlConfiguration.loadConfiguration(file);
}
/** false, wenn der Datenspeicher gerade nicht benutzbar ist (z.B. MySQL ausgefallen). */
public static synchronized boolean isReady() {
if (mysqlActive) return MySQLManager.ensureConnected();
return dataConfig != null;
}
private static void logSql(SQLException e) {
Adventskalender.getInstance().getLogger().severe("Datenbankfehler: " + e.getMessage());
}
// ------------------------------------------------------------ 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) {
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) {
for (CalendarEvent e : eventsOf(ev)) {
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;
}
@SuppressWarnings("deprecation")
private static String lookupUuid(String name) {
OfflinePlayer target = Bukkit.getOfflinePlayer(name);
return target.getUniqueId().toString();
}
private static boolean save() {
if (dataConfig == null || file == null) return false;
try {
dataConfig.save(file);
return true;
} catch (IOException e) {
Adventskalender.getInstance().getLogger().severe("data.yml konnte nicht gespeichert werden: " + e.getMessage());
return false;
}
}
}
@@ -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;
}
}
@@ -1,35 +1,76 @@
package de.mviper.adventskalender;
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 YamlConfiguration cfg;
private static String prefix;
public static void setup() {
String lang = Adventskalender.getInstance().getConfig().getString("general.language", "de");
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) {
String s = cfg.getString(path);
return s == null ? "§cKey error: " + path : ChatColor.translateAlternateColorCodes('&', s);
}
public static String getMessage(String path) {
return prefix + getString(path);
}
public static List<String> getStringList(String path) {
return cfg.getStringList(path).stream()
.map(s -> ChatColor.translateAlternateColorCodes('&', s))
.collect(Collectors.toList());
}
}
package de.mviper.adventskalender;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.YamlConfiguration;
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.stream.Collectors;
public class LanguageManager {
private static YamlConfiguration cfg;
private static String prefix;
public static void setup() {
Adventskalender plugin = Adventskalender.getInstance();
String lang = plugin.getConfig().getString("general.language", "de").toLowerCase();
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);
// 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] "));
}
public static String getString(String path) {
String s = cfg.getString(path);
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) {
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) {
return cfg.getStringList(path).stream()
.map(s -> ChatColor.translateAlternateColorCodes('&', s))
.collect(Collectors.toList());
}
}
@@ -1,66 +1,104 @@
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;
}
}
package de.mviper.adventskalender;
import org.bukkit.configuration.file.FileConfiguration;
import java.sql.*;
public class MySQLManager {
private static Connection connection;
/** Baut die Verbindung (neu) auf. Eine bestehende Verbindung wird vorher geschlossen. */
public static synchronized 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");
close();
try {
loadDriver();
connection = DriverManager.getConnection(
"jdbc:mysql://" + host + ":" + port + "/" + database + "?useSSL=false&characterEncoding=utf8", user, password);
createTables();
Adventskalender.getInstance().getLogger().info("MySQL-Verbindung erfolgreich hergestellt!");
} catch (Exception e) {
connection = null;
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() {
try (Statement s = connection.createStatement()) {
// Tabelle für Einzelspieler-Daten
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
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) {
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 synchronized boolean isConnected() {
try {
return connection != null && !connection.isClosed();
} catch (SQLException e) {
return false;
}
}
/** Prüft die Verbindung und baut sie bei Bedarf neu auf. */
public static synchronized boolean ensureConnected() {
try {
if (connection != null && connection.isValid(2)) return true;
} catch (SQLException ignored) {
}
connect();
return connection != null;
}
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;
}
}
@@ -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.plugin.java.JavaPlugin;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URI;
import java.net.URLConnection;
import java.util.Scanner;
import java.util.function.Consumer;
@@ -20,14 +20,18 @@ public class UpdateChecker {
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());
try {
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()) {
consumer.accept(scann.next());
}
}
} catch (IOException e) {
} catch (Exception e) {
plugin.getLogger().info("Update-Check fehlgeschlagen: " + e.getMessage());
}
});
}
}
}