Upload via GUI (19 Dateien)
This commit is contained in:
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user