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 days, long time) {} // Kurzer Cache, damit Scoreboards/Hologramme die Datenbank nicht jede Sekunde abfragen private final ConcurrentHashMap 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 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 days = DataHandler.getViewClaimedDays(player, ev); if (days == null) days = new HashSet<>(); cache.put(key, new Cached(days, now)); return days; } private record CachedBirthday(Optional birthday, long time) {} private final ConcurrentHashMap 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 } }