Upload via GUI (52 Dateien)
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 503 B |
Binary file not shown.
|
After Width: | Height: | Size: 391 B |
@@ -16,8 +16,25 @@ public class AdventCommand implements CommandExecutor, TabCompleter {
|
||||
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
|
||||
Adventskalender plugin = Adventskalender.getInstance();
|
||||
|
||||
// Christmas+-Kurzbefehle: /ac test und /ac reset <Spieler> <Tag|all>
|
||||
boolean elevated = false;
|
||||
if (args.length == 1 && args[0].equalsIgnoreCase("test")
|
||||
&& sender instanceof Player tp && tp.hasPermission("christmasplus.adventcalendar.test")) {
|
||||
boolean on = de.mviper.adventskalender.christmas.CalendarPerks.toggle(tp);
|
||||
var xm = de.mviper.adventskalender.christmas.ChristmasManager.get();
|
||||
tp.sendMessage(xm != null ? xm.msg(on ? "calendar-test-on" : "calendar-test-off") : (on ? "Testmodus an" : "Testmodus aus"));
|
||||
return true;
|
||||
}
|
||||
if (args.length == 3 && args[0].equalsIgnoreCase("reset")
|
||||
&& sender.hasPermission("christmasplus.adventcalendar.reset")) {
|
||||
args = args[2].equalsIgnoreCase("all")
|
||||
? new String[]{"admin", "reset", args[1]}
|
||||
: new String[]{"admin", "resetday", args[1], args[2]};
|
||||
elevated = true;
|
||||
}
|
||||
|
||||
if (args.length > 0 && args[0].equalsIgnoreCase("admin")) {
|
||||
if (!sender.hasPermission("adventskalender.admin")) {
|
||||
if (!elevated && !sender.hasPermission("adventskalender.admin")) {
|
||||
sender.sendMessage(LanguageManager.getMessage("messages.no_permission"));
|
||||
return true;
|
||||
}
|
||||
@@ -29,6 +46,7 @@ public class AdventCommand implements CommandExecutor, TabCompleter {
|
||||
LanguageManager.setup();
|
||||
DataHandler.setup();
|
||||
AdventInventory.initCache();
|
||||
de.mviper.adventskalender.christmas.ChristmasManager.reload();
|
||||
sender.sendMessage(LanguageManager.getMessage("messages.admin.reload_success"));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -95,12 +95,23 @@ public class AdventListener implements Listener {
|
||||
|
||||
Adventskalender plugin = Adventskalender.getInstance();
|
||||
|
||||
if (calendar.currentDay() < day) {
|
||||
boolean tester = de.mviper.adventskalender.christmas.CalendarPerks.isTester(player);
|
||||
if (calendar.currentDay() < day && !tester) {
|
||||
player.sendMessage(LanguageManager.getMessage("messages.day_not_available", calendar));
|
||||
player.playSound(player.getLocation(), Sound.BLOCK_CHEST_LOCKED, 1f, 1f);
|
||||
return;
|
||||
}
|
||||
|
||||
// Christmas+: vergangene Tage nur mit Recht oeffnen (optional)
|
||||
var xm = de.mviper.adventskalender.christmas.ChristmasManager.get();
|
||||
if (xm != null && !tester && calendar.currentDay() > day
|
||||
&& xm.cfg().getBoolean("calendar-extras.restrict-past-days", false)
|
||||
&& !player.hasPermission("christmasplus.adventcalendar.claimpast")) {
|
||||
player.sendMessage(xm.msg("past-day-restricted"));
|
||||
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) {
|
||||
@@ -158,6 +169,11 @@ public class AdventListener implements Listener {
|
||||
if (RewardUtil.give(player, reward)) {
|
||||
player.sendMessage(LanguageManager.getMessage("messages.inventory_full", calendar));
|
||||
}
|
||||
de.mviper.adventskalender.christmas.ChristmasExtras.give(player, calendar.rewardsPath() + "." + day);
|
||||
var xm = de.mviper.adventskalender.christmas.ChristmasManager.get();
|
||||
if (xm != null && xm.cfg().getBoolean("calendar-extras.broadcast-claim", false)) {
|
||||
Bukkit.broadcastMessage(xm.msg("calendar-claimed-broadcast", "%player%", player.getName(), "%day%", String.valueOf(day)));
|
||||
}
|
||||
player.sendMessage(LanguageManager.getMessage("messages.reward_received", calendar).replace("%day%", String.valueOf(day)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +50,14 @@ public final class Adventskalender extends JavaPlugin {
|
||||
new AdventCalendarExpansion().register();
|
||||
}
|
||||
|
||||
de.mviper.adventskalender.christmas.ChristmasManager.enable(this);
|
||||
|
||||
getLogger().info("Adventskalender erfolgreich gestartet!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
de.mviper.adventskalender.christmas.ChristmasManager.disable();
|
||||
MySQLManager.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/** Persoenlicher Testmodus (/ac test): erlaubt einem Spieler, jeden Tag zu oeffnen. */
|
||||
public final class CalendarPerks {
|
||||
private static final Set<UUID> TESTERS = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private CalendarPerks() {}
|
||||
|
||||
public static boolean isTester(Player p) {
|
||||
return TESTERS.contains(p.getUniqueId()) && p.hasPermission("christmasplus.adventcalendar.test");
|
||||
}
|
||||
|
||||
/** @return true, wenn der Testmodus jetzt an ist */
|
||||
public static boolean toggle(Player p) {
|
||||
if (!TESTERS.remove(p.getUniqueId())) {
|
||||
TESTERS.add(p.getUniqueId());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import de.mviper.adventskalender.RewardUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Verarbeitet /christmas und /cracker. */
|
||||
public final class ChristmasCommand implements CommandExecutor, TabCompleter {
|
||||
private final ChristmasManager mgr;
|
||||
|
||||
ChristmasCommand(ChristmasManager mgr) { this.mgr = mgr; }
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender s, Command cmd, String label, String[] a) {
|
||||
if (cmd.getName().equalsIgnoreCase("cracker")) return cracker(s, a);
|
||||
if (a.length == 0) {
|
||||
s.sendMessage(mgr.msg("info", "%status%", mgr.raw(mgr.trees().isEnded() ? "status-ended" : "status-active"),
|
||||
"%trees%", String.valueOf(mgr.trees().treeCount())));
|
||||
return true;
|
||||
}
|
||||
switch (a[0].toLowerCase()) {
|
||||
case "help" -> mgr.rawList("help").forEach(s::sendMessage);
|
||||
case "snow" -> snow(s);
|
||||
case "santa" -> santa(s, a);
|
||||
case "snowman" -> snowman(s, a);
|
||||
case "countdown" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return true; }
|
||||
s.sendMessage(mgr.msg(mgr.countdown().toggle(p) ? "countdown-on" : "countdown-off"));
|
||||
if (!mgr.countdown().visible()) {
|
||||
s.sendMessage(mgr.msg("countdown-not-yet", "%days%", String.valueOf(mgr.countdown().daysLeft()),
|
||||
"%in%", String.valueOf(mgr.countdown().daysUntilVisible())));
|
||||
} else {
|
||||
s.sendMessage(mgr.prefixed(mgr.countdown().text()));
|
||||
}
|
||||
}
|
||||
case "cookie" -> cookie(s, a);
|
||||
case "wichteln" -> wichteln(s, a);
|
||||
case "wreath" -> wreath(s, a);
|
||||
case "snowball" -> snowball(s, a);
|
||||
case "deco" -> deco(s, a);
|
||||
case "stocking" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return true; }
|
||||
String sub = a.length > 1 ? a[1].toLowerCase() : "";
|
||||
switch (sub) {
|
||||
case "place" -> mgr.stockings().place(p);
|
||||
case "remove" -> mgr.stockings().removeNearest(p);
|
||||
case "flight" -> {
|
||||
if (!admin(s)) return true;
|
||||
s.sendMessage(mgr.msg(mgr.stockings().flight().launch(true) ? "stocking-flight-ok" : "stocking-flight-none"));
|
||||
}
|
||||
default -> mgr.stockings().use(p);
|
||||
}
|
||||
}
|
||||
case "hat" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return true; }
|
||||
mgr.hats().give(p);
|
||||
}
|
||||
case "quiz" -> quiz(s, a);
|
||||
case "wish" -> wish(s, a);
|
||||
case "wishes" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return true; }
|
||||
if (!admin(s)) return true;
|
||||
mgr.wishes().openMenu(p, 0);
|
||||
}
|
||||
case "market" -> market(s, a);
|
||||
case "portal" -> portal(s, a);
|
||||
case "give" -> giveCrystal(s, a);
|
||||
case "gifts" -> {
|
||||
if (!admin(s)) return true;
|
||||
mgr.trees().spawnPresentsEverywhere();
|
||||
Bukkit.broadcastMessage(mgr.prefixed(mgr.raw("gifts-spawned")));
|
||||
}
|
||||
case "addhand" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return true; }
|
||||
if (!admin(s)) return true;
|
||||
s.sendMessage(mgr.msg(mgr.trees().addGift(p.getInventory().getItemInMainHand()) ? "addhand-ok" : "addhand-empty"));
|
||||
}
|
||||
case "end" -> {
|
||||
if (!admin(s)) return true;
|
||||
mgr.trees().setEnded(true);
|
||||
s.sendMessage(mgr.msg("end-set"));
|
||||
}
|
||||
case "reload" -> {
|
||||
if (!s.hasPermission("christmasplus.christmas.reload") && !s.hasPermission("xmas.admin")) {
|
||||
s.sendMessage(mgr.msg("no-permission"));
|
||||
return true;
|
||||
}
|
||||
ChristmasManager.reload();
|
||||
s.sendMessage(mgr.msg("reloaded"));
|
||||
}
|
||||
default -> s.sendMessage(mgr.msg("usage"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ---------- Plaetzchen ----------
|
||||
|
||||
private void cookie(CommandSender s, String[] a) {
|
||||
if (!admin(s)) return;
|
||||
CookieManager cm = mgr.cookies();
|
||||
if (a.length >= 2 && a[1].equalsIgnoreCase("list")) {
|
||||
s.sendMessage(mgr.prefixed(String.join(", ", cm.ids())));
|
||||
return;
|
||||
}
|
||||
if (a.length < 4 || !a[1].equalsIgnoreCase("give")) { s.sendMessage(mgr.msg("cookie-usage")); return; }
|
||||
Player t = Bukkit.getPlayerExact(a[2]);
|
||||
if (t == null) { s.sendMessage(mgr.msg("player-offline")); return; }
|
||||
int amount = a.length > 4 ? Math.max(1, Math.min(64, ChristmasManager.toInt(a[4], 1))) : 1;
|
||||
ItemStack it = cm.create(a[3], amount);
|
||||
if (it == null) { s.sendMessage(mgr.msg("cookie-unknown", "%id%", a[3])); return; }
|
||||
RewardUtil.give(t, it);
|
||||
t.sendMessage(mgr.msg("cookie-given", "%amount%", String.valueOf(amount), "%id%", a[3]));
|
||||
}
|
||||
|
||||
// ---------- Wichteln ----------
|
||||
|
||||
private void wichteln(CommandSender s, String[] a) {
|
||||
WichtelnManager w = mgr.wichteln();
|
||||
String sub = a.length > 1 ? a[1].toLowerCase() : "status";
|
||||
boolean adminCmd = sub.equals("open") || sub.equals("draw") || sub.equals("deliver") || sub.equals("reset");
|
||||
if (adminCmd && !admin(s)) return;
|
||||
if (!adminCmd && !perm(s, "xmas.use")) return;
|
||||
|
||||
switch (sub) {
|
||||
case "open" -> {
|
||||
if (!w.open()) { s.sendMessage(mgr.msg("wi-wrong-state")); return; }
|
||||
Bukkit.broadcastMessage(mgr.msg("wi-open"));
|
||||
}
|
||||
case "draw" -> {
|
||||
if (w.state() != WichtelnManager.State.OPEN) { s.sendMessage(mgr.msg("wi-wrong-state")); return; }
|
||||
if (!w.draw()) { s.sendMessage(mgr.msg("wi-too-few")); return; }
|
||||
Bukkit.broadcastMessage(mgr.msg("wi-drawn"));
|
||||
}
|
||||
case "deliver" -> {
|
||||
int n = w.deliver();
|
||||
if (n < 0) { s.sendMessage(mgr.msg("wi-wrong-state")); return; }
|
||||
Bukkit.broadcastMessage(mgr.msg("wi-delivered", "%count%", String.valueOf(n)));
|
||||
}
|
||||
case "reset" -> { w.reset(); s.sendMessage(mgr.msg("wi-reset")); }
|
||||
case "join" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
if (w.state() != WichtelnManager.State.OPEN) { s.sendMessage(mgr.msg("wi-wrong-state")); return; }
|
||||
boolean already = w.joined(p.getUniqueId());
|
||||
w.join(p);
|
||||
s.sendMessage(mgr.msg(already ? "wi-already" : "wi-joined"));
|
||||
}
|
||||
case "leave" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
s.sendMessage(mgr.msg(w.leave(p) ? "wi-left" : "wi-not-joined"));
|
||||
}
|
||||
case "who" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
String name = w.targetName(p.getUniqueId());
|
||||
s.sendMessage(name.isEmpty() ? mgr.msg("wi-not-joined") : mgr.msg("wi-who", "%name%", name));
|
||||
}
|
||||
case "gift" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
String key = w.setGift(p);
|
||||
s.sendMessage(mgr.msg(key, "%name%", w.targetName(p.getUniqueId())));
|
||||
}
|
||||
default -> s.sendMessage(mgr.msg("wi-status", "%state%", w.state().name(),
|
||||
"%count%", String.valueOf(w.participantCount()), "%gifts%", String.valueOf(w.giftCount())));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Adventskranz ----------
|
||||
|
||||
private void wreath(CommandSender s, String[] a) {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
if (!admin(s)) return;
|
||||
org.bukkit.block.Block target = p.getTargetBlockExact(8);
|
||||
if (target == null) { s.sendMessage(mgr.msg("deco-look")); return; }
|
||||
String sub = a.length > 1 ? a[1].toLowerCase() : "";
|
||||
if (sub.equals("create")) {
|
||||
mgr.wreaths().create(target);
|
||||
s.sendMessage(mgr.msg("wreath-created"));
|
||||
} else if (sub.equals("remove")) {
|
||||
s.sendMessage(mgr.msg(mgr.wreaths().removeNear(target) ? "wreath-removed" : "wreath-none"));
|
||||
} else {
|
||||
s.sendMessage(mgr.msg("wreath-usage"));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Schneeball-Spiel ----------
|
||||
|
||||
private void snowball(CommandSender s, String[] a) {
|
||||
SnowballGame g = mgr.snowball();
|
||||
String sub = a.length > 1 ? a[1].toLowerCase() : "top";
|
||||
if (sub.equals("top")) {
|
||||
List<String> lines = g.topLines(5);
|
||||
if (lines.isEmpty()) s.sendMessage(mgr.msg("sb-no-scores")); else lines.forEach(s::sendMessage);
|
||||
return;
|
||||
}
|
||||
if (!admin(s)) return;
|
||||
switch (sub) {
|
||||
case "arena" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
int r = a.length > 2 ? ChristmasManager.toInt(a[2], 15) : 15;
|
||||
g.setArena(p.getLocation(), r);
|
||||
s.sendMessage(mgr.msg("sb-arena-set", "%radius%", String.valueOf(Math.max(3, r))));
|
||||
}
|
||||
case "start" -> {
|
||||
if (!g.hasArena()) { s.sendMessage(mgr.msg("sb-no-arena")); return; }
|
||||
int time = mgr.cfg().getInt("snowball.duration", 180);
|
||||
if (a.length > 2 && ChristmasManager.parseDuration(a[2]) > 0) time = ChristmasManager.parseDuration(a[2]);
|
||||
if (!g.start(time)) s.sendMessage(mgr.msg("sb-already"));
|
||||
}
|
||||
case "stop" -> {
|
||||
if (!g.isActive()) { s.sendMessage(mgr.msg("sb-not-active")); return; }
|
||||
g.stop(true);
|
||||
}
|
||||
default -> s.sendMessage(mgr.msg("sb-usage"));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Deko ----------
|
||||
|
||||
private void deco(CommandSender s, String[] a) {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
if (!admin(s)) return;
|
||||
if (a.length > 1 && a[1].equalsIgnoreCase("undo")) mgr.deco().undoLast(p);
|
||||
else mgr.deco().open(p);
|
||||
}
|
||||
|
||||
|
||||
// ---------- Quiz ----------
|
||||
|
||||
private void quiz(CommandSender s, String[] a) {
|
||||
if (!admin(s)) return;
|
||||
QuizManager q = mgr.quiz();
|
||||
String sub = a.length > 1 ? a[1].toLowerCase() : "";
|
||||
switch (sub) {
|
||||
case "start" -> {
|
||||
int idx = a.length > 2 ? ChristmasManager.toInt(a[2], 0) - 1 : -1;
|
||||
if (q.isActive()) { s.sendMessage(mgr.msg("quiz-already")); return; }
|
||||
if (!q.ask(idx)) s.sendMessage(mgr.msg("quiz-none"));
|
||||
}
|
||||
case "skip", "stop" -> s.sendMessage(mgr.msg(q.skip() ? "quiz-stopped" : "quiz-not-active"));
|
||||
case "count" -> s.sendMessage(mgr.prefixed(String.valueOf(q.questionCount())));
|
||||
default -> s.sendMessage(mgr.msg("quiz-usage"));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Wunschzettel ----------
|
||||
|
||||
private void wish(CommandSender s, String[] a) {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
if (!perm(s, "xmas.use")) return;
|
||||
if (a.length < 2) { s.sendMessage(mgr.msg("wish-usage")); return; }
|
||||
if (a[1].equalsIgnoreCase("list")) { mgr.wishes().list(p); return; }
|
||||
if (a[1].equalsIgnoreCase("clear")) {
|
||||
s.sendMessage(mgr.msg("wish-cleared", "%count%", String.valueOf(mgr.wishes().clearOwn(p))));
|
||||
return;
|
||||
}
|
||||
String text = String.join(" ", Arrays.copyOfRange(a, 1, a.length));
|
||||
s.sendMessage(mgr.msg(mgr.wishes().add(p, text), "%max%", String.valueOf(mgr.cfg().getInt("wishes.max-length", 120))));
|
||||
}
|
||||
|
||||
// ---------- Marktstaende ----------
|
||||
|
||||
private void market(CommandSender s, String[] a) {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
String sub = a.length > 1 ? a[1].toLowerCase() : "";
|
||||
if (sub.equals("back")) { // fuer alle Spieler
|
||||
s.sendMessage(mgr.msg(mgr.portals().back(p)));
|
||||
return;
|
||||
}
|
||||
if (!admin(s)) return;
|
||||
switch (sub) {
|
||||
case "list" -> s.sendMessage(mgr.prefixed(String.join(", ", mgr.market().stallIds())));
|
||||
case "remove" -> s.sendMessage(mgr.msg(mgr.market().removeNearest(p, 6) ? "market-removed" : "market-none"));
|
||||
case "set" -> {
|
||||
mgr.market().setSpawn(p.getLocation());
|
||||
s.sendMessage(mgr.msg("market-set"));
|
||||
}
|
||||
case "unset" -> {
|
||||
mgr.market().clearSpawn();
|
||||
s.sendMessage(mgr.msg("market-unset"));
|
||||
}
|
||||
case "tp" -> {
|
||||
Location dest = mgr.market().spawn();
|
||||
if (dest == null) { s.sendMessage(mgr.msg("portal-no-market")); return; }
|
||||
p.teleport(dest);
|
||||
}
|
||||
case "spawn" -> {
|
||||
if (a.length < 3) { s.sendMessage(mgr.msg("market-usage")); return; }
|
||||
if (!mgr.market().spawn(p.getLocation(), a[2])) { s.sendMessage(mgr.msg("market-unknown", "%id%", a[2])); return; }
|
||||
s.sendMessage(mgr.msg("market-spawned"));
|
||||
}
|
||||
default -> s.sendMessage(mgr.msg("market-usage"));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Portale ----------
|
||||
|
||||
private void portal(CommandSender s, String[] a) {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
if (!perm(s, "xmas.use")) return;
|
||||
String sub = a.length > 1 ? a[1].toLowerCase() : "";
|
||||
org.bukkit.block.Block target = p.getTargetBlockExact(6);
|
||||
switch (sub) {
|
||||
case "create" -> {
|
||||
if (target == null) { s.sendMessage(mgr.msg("deco-look")); return; }
|
||||
s.sendMessage(mgr.msg(mgr.portals().create(p, target), "%cost%",
|
||||
String.join(", ", mgr.cfg().getStringList("portal.cost")),
|
||||
"%limit%", String.valueOf(mgr.cfg().getInt("portal.limit-per-player", 1))));
|
||||
}
|
||||
case "remove" -> {
|
||||
if (target == null) { s.sendMessage(mgr.msg("portal-none")); return; }
|
||||
s.sendMessage(mgr.msg(mgr.portals().remove(p, target)));
|
||||
}
|
||||
case "info" -> s.sendMessage(mgr.msg("portal-info", "%cost%", String.join(", ", mgr.cfg().getStringList("portal.cost")),
|
||||
"%count%", String.valueOf(mgr.portals().countOf(p.getUniqueId())),
|
||||
"%limit%", String.valueOf(mgr.cfg().getInt("portal.limit-per-player", 1))));
|
||||
default -> s.sendMessage(mgr.msg("portal-usage"));
|
||||
}
|
||||
}
|
||||
|
||||
private void snowman(CommandSender s, String[] a) {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
if (!admin(s)) return;
|
||||
if (a.length < 2) { s.sendMessage(mgr.msg("snowman-usage")); return; }
|
||||
switch (a[1].toLowerCase()) {
|
||||
case "spawn" -> {
|
||||
int from = 2;
|
||||
Boolean walk = null;
|
||||
if (a.length > 2) {
|
||||
String m = a[2].toLowerCase();
|
||||
if (m.equals("walk") || m.equals("laufen")) { walk = true; from = 3; }
|
||||
else if (m.equals("stand") || m.equals("stehen")) { walk = false; from = 3; }
|
||||
}
|
||||
String name = a.length > from ? String.join(" ", Arrays.copyOfRange(a, from, a.length)) : "";
|
||||
mgr.snowmen().spawnDeco(p.getLocation(), name, walk);
|
||||
s.sendMessage(mgr.msg("snowman-spawned"));
|
||||
}
|
||||
case "remove" -> s.sendMessage(mgr.msg(mgr.snowmen().removeNearestDeco(p, 6) ? "snowman-removed" : "snowman-none"));
|
||||
default -> s.sendMessage(mgr.msg("snowman-usage"));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean admin(CommandSender s) {
|
||||
return perm(s, "xmas.admin");
|
||||
}
|
||||
|
||||
private void giveCrystal(CommandSender s, String[] a) {
|
||||
if (!admin(s)) return;
|
||||
if (a.length < 2) { s.sendMessage(mgr.msg("usage")); return; }
|
||||
Player t = Bukkit.getPlayerExact(a[1]);
|
||||
if (t == null) { s.sendMessage(mgr.msg("player-offline")); return; }
|
||||
int amount = 1;
|
||||
if (a.length > 2 && a[2].matches("\\d+")) amount = Math.max(1, Math.min(64, Integer.parseInt(a[2])));
|
||||
RewardUtil.give(t, mgr.trees().crystal(amount));
|
||||
t.sendMessage(mgr.msg("crystal-given", "%amount%", String.valueOf(amount)));
|
||||
if (s != t) s.sendMessage(mgr.msg("crystal-gave", "%player%", t.getName(), "%amount%", String.valueOf(amount)));
|
||||
}
|
||||
|
||||
private boolean perm(CommandSender s, String p) {
|
||||
if (s.hasPermission(p)) return true;
|
||||
s.sendMessage(mgr.msg("no-permission"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------- Snow ----------
|
||||
|
||||
private void snow(CommandSender s) {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
if (!perm(s, "christmasplus.christmas.snow")) return;
|
||||
if (!mgr.cfg().getBoolean("snow.enable", true)) { s.sendMessage(mgr.msg("snow-disabled-global")); return; }
|
||||
s.sendMessage(mgr.msg(mgr.toggleSnow(p) ? "snow-on" : "snow-off"));
|
||||
}
|
||||
|
||||
// ---------- Santa ----------
|
||||
|
||||
private void santa(CommandSender s, String[] a) {
|
||||
if (!perm(s, "christmasplus.christmas.santa")) return;
|
||||
if (a.length < 2) { s.sendMessage(mgr.msg("santa-usage")); return; }
|
||||
SantaEvent santa = mgr.santa();
|
||||
switch (a[1].toLowerCase()) {
|
||||
case "stop" -> {
|
||||
if (!santa.isActive()) s.sendMessage(mgr.msg("santa-not-active"));
|
||||
else santa.stop(true);
|
||||
}
|
||||
case "teleport" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return; }
|
||||
if (!santa.isActive()) { s.sendMessage(mgr.msg("santa-not-active")); return; }
|
||||
p.teleport(santa.getLocation());
|
||||
p.sendMessage(mgr.msg("santa-teleported"));
|
||||
}
|
||||
case "start" -> {
|
||||
if (santa.isActive()) { s.sendMessage(mgr.msg("santa-already")); return; }
|
||||
int time = mgr.cfg().getInt("santa.duration", 300);
|
||||
int i = 2;
|
||||
if (a.length > i && ChristmasManager.parseDuration(a[i]) > 0) time = ChristmasManager.parseDuration(a[i++]);
|
||||
Location where = null;
|
||||
if (a.length > i && a[i].equalsIgnoreCase("random")) {
|
||||
where = santa.randomLocation();
|
||||
if (where == null) { s.sendMessage(mgr.msg("santa-no-locations")); return; }
|
||||
} else if (a.length >= i + 4) {
|
||||
World w = Bukkit.getWorld(a[i]);
|
||||
try {
|
||||
if (w != null) where = new Location(w, Double.parseDouble(a[i + 1]),
|
||||
Double.parseDouble(a[i + 2]), Double.parseDouble(a[i + 3]));
|
||||
} catch (NumberFormatException ignored) { }
|
||||
} else if (s instanceof Player p) {
|
||||
where = p.getLocation();
|
||||
}
|
||||
if (where == null) { s.sendMessage(mgr.msg("santa-usage")); return; }
|
||||
santa.start(time, where);
|
||||
s.sendMessage(mgr.msg("santa-started-sender", "%duration%", SantaEvent.formatDuration(time)));
|
||||
}
|
||||
default -> s.sendMessage(mgr.msg("santa-usage"));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Cracker ----------
|
||||
|
||||
private boolean cracker(CommandSender s, String[] a) {
|
||||
CrackerManager cm = mgr.crackers();
|
||||
if (a.length == 0) { s.sendMessage(mgr.msg("cracker-usage")); return true; }
|
||||
switch (a[0].toLowerCase()) {
|
||||
case "list" -> {
|
||||
if (!perm(s, "christmasplus.cracker.list")) return true;
|
||||
s.sendMessage(mgr.prefixed(String.join(", ", cm.ids())));
|
||||
}
|
||||
case "get" -> {
|
||||
if (!(s instanceof Player p)) { s.sendMessage(mgr.msg("player-only")); return true; }
|
||||
if (!perm(s, "christmasplus.cracker.get")) return true;
|
||||
String id = cm.firstId();
|
||||
int amount = 1;
|
||||
for (int i = 1; i < a.length; i++) {
|
||||
if (a[i].matches("\\d+")) amount = Integer.parseInt(a[i]); else id = a[i];
|
||||
}
|
||||
if (amount > 1 && !perm(s, "christmasplus.cracker.get.multiple")) return true;
|
||||
giveCracker(s, p, id, amount);
|
||||
}
|
||||
case "give" -> {
|
||||
if (!perm(s, "christmasplus.cracker.give")) return true;
|
||||
if (a.length < 2) { s.sendMessage(mgr.msg("cracker-usage")); return true; }
|
||||
Player t = Bukkit.getPlayerExact(a[1]);
|
||||
if (t == null) { s.sendMessage(mgr.msg("player-not-found")); return true; }
|
||||
String id = cm.firstId();
|
||||
int amount = 1;
|
||||
for (int i = 2; i < a.length; i++) {
|
||||
if (a[i].matches("\\d+")) amount = Integer.parseInt(a[i]); else id = a[i];
|
||||
}
|
||||
giveCracker(s, t, id, amount);
|
||||
}
|
||||
default -> s.sendMessage(mgr.msg("cracker-usage"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void giveCracker(CommandSender s, Player target, String id, int amount) {
|
||||
amount = Math.max(1, Math.min(amount, 64));
|
||||
ItemStack it = id == null ? null : mgr.crackers().create(id, amount);
|
||||
if (it == null) { s.sendMessage(mgr.msg("cracker-unknown", "%id%", String.valueOf(id))); return; }
|
||||
RewardUtil.give(target, it);
|
||||
target.sendMessage(mgr.msg("cracker-given", "%amount%", String.valueOf(amount), "%id%", id));
|
||||
if (s != target) s.sendMessage(mgr.msg("cracker-gave", "%player%", target.getName(),
|
||||
"%amount%", String.valueOf(amount), "%id%", id));
|
||||
}
|
||||
|
||||
// ---------- Tab ----------
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender s, Command cmd, String label, String[] a) {
|
||||
List<String> out = new ArrayList<>();
|
||||
if (cmd.getName().equalsIgnoreCase("cracker")) {
|
||||
if (a.length == 1) out.addAll(Arrays.asList("list", "get", "give"));
|
||||
else if (a[0].equalsIgnoreCase("give") && a.length == 2)
|
||||
Bukkit.getOnlinePlayers().forEach(p -> out.add(p.getName()));
|
||||
else out.addAll(mgr.crackers().ids());
|
||||
} else if (a.length == 1) {
|
||||
out.addAll(Arrays.asList("help", "snow", "santa", "give", "gifts", "addhand", "snowman", "countdown", "cookie", "wichteln", "wreath", "snowball", "deco", "stocking", "hat", "quiz", "wish", "wishes", "market", "portal", "end", "reload"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("santa")) {
|
||||
out.addAll(Arrays.asList("start", "stop", "teleport"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("stocking")) {
|
||||
out.addAll(Arrays.asList("place", "remove", "flight"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("snowman")) {
|
||||
out.addAll(Arrays.asList("spawn", "remove"));
|
||||
} else if (a.length == 3 && a[0].equalsIgnoreCase("snowman") && a[1].equalsIgnoreCase("spawn")) {
|
||||
out.addAll(Arrays.asList("stand", "walk"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("cookie")) {
|
||||
out.addAll(Arrays.asList("give", "list"));
|
||||
} else if (a.length == 4 && a[0].equalsIgnoreCase("cookie")) {
|
||||
out.addAll(mgr.cookies().ids());
|
||||
} else if (a.length == 3 && a[0].equalsIgnoreCase("cookie")) {
|
||||
Bukkit.getOnlinePlayers().forEach(p -> out.add(p.getName()));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("wichteln")) {
|
||||
out.addAll(Arrays.asList("join", "leave", "who", "gift", "status", "open", "draw", "deliver", "reset"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("wreath")) {
|
||||
out.addAll(Arrays.asList("create", "remove"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("snowball")) {
|
||||
out.addAll(Arrays.asList("top", "arena", "start", "stop"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("deco")) {
|
||||
out.add("undo");
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("portal")) {
|
||||
out.addAll(Arrays.asList("create", "remove", "info"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("quiz")) {
|
||||
out.addAll(Arrays.asList("start", "skip", "count"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("wish")) {
|
||||
out.addAll(Arrays.asList("list", "clear"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("market")) {
|
||||
out.addAll(Arrays.asList("spawn", "remove", "list", "set", "unset", "tp", "back"));
|
||||
} else if (a.length == 3 && a[0].equalsIgnoreCase("market") && a[1].equalsIgnoreCase("spawn")) {
|
||||
out.addAll(mgr.market().stallIds());
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("give")) {
|
||||
Bukkit.getOnlinePlayers().forEach(p -> out.add(p.getName()));
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String last = a[a.length - 1].toLowerCase();
|
||||
out.removeIf(x -> !x.toLowerCase().startsWith(last));
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import de.mviper.adventskalender.Adventskalender;
|
||||
import de.mviper.adventskalender.RewardUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Zusatz-Belohnungen fuer Kalendertage (Christmas+ Funktionen). Im Abschnitt rewards.<Tag> der config.yml moeglich:
|
||||
* <pre>
|
||||
* commands: ["give %player% apple 3"]
|
||||
* permissions: ["essentials.fly"] # wird ueber calendar-extras.permission-command vergeben
|
||||
* multiple: ["DIAMOND:2", "COOKIE:8"] # mehrere Geschenke am Tag
|
||||
* random: # zufaelliges Zusatzgeschenk (chance = Gewicht)
|
||||
* - { chance: 50, item: "GOLD_INGOT:4" }
|
||||
* - { chance: 50, command: "say Hallo %player%" }
|
||||
* cracker: "cracker:2" # Cracker-ID und Anzahl
|
||||
* crystal: 1 # Weihnachts-Kristall
|
||||
* groups: # Extra-Geschenke nur fuer bestimmte Gruppen (z.B. Donor)
|
||||
* donor:
|
||||
* permission: "christmasplus.donor"
|
||||
* material: EMERALD # optional, wie das normale Geschenk
|
||||
* amount: 3
|
||||
* multiple: ["DIAMOND:1"] # und alle Schluessel von oben
|
||||
* </pre>
|
||||
*/
|
||||
public final class ChristmasExtras {
|
||||
private ChristmasExtras() {}
|
||||
|
||||
public static void give(Player p, String path) {
|
||||
ConfigurationSection s = Adventskalender.getInstance().getConfig().getConfigurationSection(path);
|
||||
if (s == null) return;
|
||||
apply(p, s);
|
||||
|
||||
ConfigurationSection groups = s.getConfigurationSection("groups");
|
||||
if (groups == null) return;
|
||||
for (String name : groups.getKeys(false)) {
|
||||
ConfigurationSection g = groups.getConfigurationSection(name);
|
||||
if (g == null) continue;
|
||||
String perm = g.getString("permission", "");
|
||||
if (!perm.isBlank() && !p.hasPermission(perm)) continue;
|
||||
if (g.contains("material")) {
|
||||
ItemStack it = RewardUtil.build(path + ".groups." + name, "Gruppe " + name);
|
||||
if (it != null) RewardUtil.give(p, it);
|
||||
}
|
||||
apply(p, g);
|
||||
}
|
||||
}
|
||||
|
||||
/** Wendet commands, permissions, multiple, random, cracker und crystal eines Abschnitts an. */
|
||||
public static void apply(Player p, ConfigurationSection s) {
|
||||
for (String cmd : s.getStringList("commands")) run(p, cmd);
|
||||
for (String perm : s.getStringList("permissions")) grantPermission(p, perm);
|
||||
|
||||
for (String spec : s.getStringList("multiple")) {
|
||||
ItemStack it = ChristmasManager.parseItem(spec);
|
||||
if (it != null) RewardUtil.give(p, it);
|
||||
}
|
||||
|
||||
List<Map<?, ?>> random = s.getMapList("random");
|
||||
double sum = 0;
|
||||
for (Map<?, ?> r : random) sum += weight(r);
|
||||
if (sum > 0) {
|
||||
double roll = ChristmasManager.RANDOM.nextDouble() * sum;
|
||||
for (Map<?, ?> r : random) {
|
||||
roll -= weight(r);
|
||||
if (roll > 0) continue;
|
||||
applyMap(p, r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ChristmasManager mgr = ChristmasManager.get();
|
||||
if (mgr == null) return;
|
||||
String cracker = s.getString("cracker");
|
||||
if (cracker != null && !cracker.isBlank()) {
|
||||
String[] parts = cracker.split(":");
|
||||
int amount = 1;
|
||||
if (parts.length > 1) {
|
||||
try { amount = Integer.parseInt(parts[1].trim()); } catch (NumberFormatException ignored) { }
|
||||
}
|
||||
ItemStack it = mgr.crackers().create(parts[0].trim(), amount);
|
||||
if (it != null) RewardUtil.give(p, it);
|
||||
}
|
||||
int crystal = s.getInt("crystal", 0);
|
||||
if (crystal > 0) RewardUtil.give(p, mgr.trees().crystal(crystal));
|
||||
}
|
||||
|
||||
/** Eintrag mit den Schluesseln item, command, permission. */
|
||||
public static void applyMap(Player p, Map<?, ?> r) {
|
||||
if (r.get("item") != null) {
|
||||
ItemStack it = ChristmasManager.parseItem(String.valueOf(r.get("item")));
|
||||
if (it != null) RewardUtil.give(p, it);
|
||||
}
|
||||
if (r.get("command") != null) run(p, String.valueOf(r.get("command")));
|
||||
if (r.get("permission") != null) grantPermission(p, String.valueOf(r.get("permission")));
|
||||
}
|
||||
|
||||
public static void grantPermission(Player p, String permission) {
|
||||
ChristmasManager mgr = ChristmasManager.get();
|
||||
String template = mgr == null ? "" : mgr.cfg().getString("permission-command", "");
|
||||
if (template == null || template.isBlank()) {
|
||||
Adventskalender.getInstance().getLogger().warning(
|
||||
"Permission '" + permission + "' soll vergeben werden, aber 'permission-command' ist in christmas.yml leer.");
|
||||
return;
|
||||
}
|
||||
run(p, template.replace("%permission%", permission));
|
||||
}
|
||||
|
||||
private static void run(Player p, String cmd) {
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd.replace("%player%", p.getName()));
|
||||
}
|
||||
|
||||
private static double weight(Map<?, ?> r) {
|
||||
Object c = r.get("chance");
|
||||
return c instanceof Number n ? Math.max(0, n.doubleValue()) : 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Tag;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.entity.Snowball;
|
||||
import org.bukkit.entity.Snowman;
|
||||
import org.bukkit.event.entity.ProjectileHitEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.event.player.PlayerItemConsumeEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.event.player.PlayerShearEntityEvent;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockBurnEvent;
|
||||
import org.bukkit.event.block.BlockExplodeEvent;
|
||||
import org.bukkit.event.block.BlockPistonExtendEvent;
|
||||
import org.bukkit.event.block.BlockPistonRetractEvent;
|
||||
import org.bukkit.event.block.LeavesDecayEvent;
|
||||
import org.bukkit.event.entity.EntityExplodeEvent;
|
||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.world.StructureGrowEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public final class ChristmasListener implements Listener {
|
||||
private final ChristmasManager mgr;
|
||||
|
||||
ChristmasListener(ChristmasManager mgr) { this.mgr = mgr; }
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onInteract(PlayerInteractEvent e) {
|
||||
if (e.getHand() != EquipmentSlot.HAND) return;
|
||||
Action a = e.getAction();
|
||||
Player p = e.getPlayer();
|
||||
MagicTreeManager trees = mgr.trees();
|
||||
|
||||
// Cracker oeffnen
|
||||
if (a == Action.RIGHT_CLICK_AIR || a == Action.RIGHT_CLICK_BLOCK) {
|
||||
ItemStack inHand = e.getItem();
|
||||
String id = mgr.crackers().idOf(inHand);
|
||||
if (id != null) {
|
||||
e.setCancelled(true);
|
||||
inHand.setAmount(inHand.getAmount() - 1);
|
||||
mgr.crackers().open(p, id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (a != Action.RIGHT_CLICK_BLOCK || e.getClickedBlock() == null) return;
|
||||
Block b = e.getClickedBlock();
|
||||
|
||||
// Geschenk unter einer Socke oeffnen
|
||||
if (mgr.stockings().isPresentBlock(b)) {
|
||||
e.setCancelled(true);
|
||||
mgr.stockings().openPresent(p, b);
|
||||
return;
|
||||
}
|
||||
|
||||
// Geschenk oeffnen
|
||||
if (trees.isPresentBlock(b)) {
|
||||
e.setCancelled(true);
|
||||
trees.openPresent(p, trees.treeOfPresent(b), b);
|
||||
return;
|
||||
}
|
||||
|
||||
// Kristall auf Setzling
|
||||
if (trees.isCrystal(e.getItem())) {
|
||||
e.setCancelled(true);
|
||||
if (Tag.SAPLINGS.isTagged(b.getType()) && !trees.isTreeBlock(b)) {
|
||||
ItemStack inHand = e.getItem();
|
||||
if (!p.hasPermission("xmas.use")) { p.sendMessage(mgr.msg("no-permission")); return; }
|
||||
int before = trees.treeCount();
|
||||
trees.plant(p, b);
|
||||
if (trees.treeCount() > before) inHand.setAmount(inHand.getAmount() - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Baum: Rechtsklick = wachsen / Info
|
||||
if (trees.isTreeBlock(b)) {
|
||||
e.setCancelled(true);
|
||||
MagicTreeManager.Tree t = trees.treeOf(b);
|
||||
if (p.isSneaking()) trees.info(p, t);
|
||||
else trees.click(p, t);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onBreak(BlockBreakEvent e) {
|
||||
MagicTreeManager trees = mgr.trees();
|
||||
Block b = e.getBlock();
|
||||
if (mgr.stockings().isPresentBlock(b)) {
|
||||
e.setCancelled(true);
|
||||
mgr.stockings().openPresent(e.getPlayer(), b);
|
||||
return;
|
||||
}
|
||||
if (trees.isPresentBlock(b)) {
|
||||
e.setCancelled(true);
|
||||
trees.openPresent(e.getPlayer(), trees.treeOfPresent(b), b);
|
||||
return;
|
||||
}
|
||||
if (trees.isTreeBlock(b)) {
|
||||
e.setCancelled(true);
|
||||
trees.breakAttempt(e.getPlayer(), trees.treeOf(b), b);
|
||||
return;
|
||||
}
|
||||
if ((mgr.wreaths().isProtected(b) || mgr.portals().isProtected(b)) && !e.getPlayer().hasPermission("xmas.admin")) {
|
||||
e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDrop(PlayerDropItemEvent e) {
|
||||
Item item = e.getItemDrop();
|
||||
Player p = e.getPlayer();
|
||||
Bukkit.getScheduler().runTaskLater(mgr.plugin(), () -> {
|
||||
if (item.isValid() && p.isOnline()) mgr.trees().absorb(p, item);
|
||||
}, 40L);
|
||||
}
|
||||
|
||||
// ---------- Schutz der Baeume und Geschenke ----------
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPistonExtend(BlockPistonExtendEvent e) {
|
||||
for (Block b : e.getBlocks()) if (mgr.isProtected(b)) { e.setCancelled(true); return; }
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPistonRetract(BlockPistonRetractEvent e) {
|
||||
for (Block b : e.getBlocks()) if (mgr.isProtected(b)) { e.setCancelled(true); return; }
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onExplode(EntityExplodeEvent e) {
|
||||
e.blockList().removeIf(b -> mgr.isProtected(b));
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockExplode(BlockExplodeEvent e) {
|
||||
e.blockList().removeIf(b -> mgr.isProtected(b));
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBurn(BlockBurnEvent e) {
|
||||
if (mgr.isProtected(e.getBlock())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onDecay(LeavesDecayEvent e) {
|
||||
if (mgr.isProtected(e.getBlock())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onStructureGrow(StructureGrowEvent e) {
|
||||
if (mgr.isProtected(e.getLocation().getBlock())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
// ---------- Schneemaenner ----------
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onSnowmanBuilt(CreatureSpawnEvent e) {
|
||||
if (e.getSpawnReason() == CreatureSpawnEvent.SpawnReason.BUILD_SNOWMAN && e.getEntity() instanceof Snowman s) {
|
||||
// Eine Tick spaeter, damit der Schneemann sicher existiert
|
||||
Bukkit.getScheduler().runTask(mgr.plugin(), () -> { if (s.isValid()) mgr.snowmen().onBuilt(s); });
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onShear(PlayerShearEntityEvent e) {
|
||||
if (mgr.snowmen().isDeco(e.getEntity())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
// ---------- Join, Plaetzchen, Schneeball, Deko-Menue ----------
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
Player p = e.getPlayer();
|
||||
mgr.countdown().onJoin(p);
|
||||
mgr.streaks().onJoin(p);
|
||||
mgr.greetings().onJoin(p);
|
||||
mgr.hats().onJoin(p);
|
||||
Bukkit.getScheduler().runTaskLater(mgr.plugin(), () -> {
|
||||
if (!p.isOnline()) return;
|
||||
mgr.stockings().tryDeliver(p, false);
|
||||
mgr.wishes().deliver(p);
|
||||
}, 140L);
|
||||
Bukkit.getScheduler().runTaskLater(mgr.plugin(), () -> { if (p.isOnline()) mgr.wichteln().onJoin(p); }, 100L);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onConsume(PlayerItemConsumeEvent e) {
|
||||
String id = mgr.cookies().idOf(e.getItem());
|
||||
if (id != null) mgr.cookies().eat(e.getPlayer(), id);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onSnowballHit(ProjectileHitEvent e) {
|
||||
if (e.getEntity() instanceof Snowball sb && sb.getShooter() instanceof Player shooter
|
||||
&& e.getHitEntity() instanceof Player victim) {
|
||||
mgr.snowball().onHit(shooter, victim);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onMenuClick(InventoryClickEvent e) {
|
||||
if (!(e.getInventory().getHolder() instanceof DecoManager.Menu)) return;
|
||||
e.setCancelled(true);
|
||||
if (e.getWhoClicked() instanceof Player p && e.getClickedInventory() == e.getInventory()
|
||||
&& p.hasPermission("xmas.admin")) {
|
||||
mgr.deco().click(p, e.getRawSlot());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onMenuDrag(InventoryDragEvent e) {
|
||||
if (e.getInventory().getHolder() instanceof DecoManager.Menu) e.setCancelled(true);
|
||||
}
|
||||
|
||||
// ---------- Quiz, Feuerwerk, Wunschzettel-Menue ----------
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onChat(AsyncPlayerChatEvent e) {
|
||||
if (mgr.quiz().onChat(e.getPlayer(), e.getMessage())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onSnowmanTrail(org.bukkit.event.block.EntityBlockFormEvent e) {
|
||||
mgr.snowmen().onSnowForm(e);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onFireworkDamage(EntityDamageByEntityEvent e) {
|
||||
if (mgr.greetings().isOurs(e.getDamager())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWishMenuClick(InventoryClickEvent e) {
|
||||
if (!(e.getInventory().getHolder() instanceof WishManager.Menu menu)) return;
|
||||
e.setCancelled(true);
|
||||
if (e.getWhoClicked() instanceof Player p && e.getClickedInventory() == e.getInventory()
|
||||
&& p.hasPermission("xmas.admin")) {
|
||||
mgr.wishes().click(p, menu, e.getRawSlot(), e.isRightClick(), e.isShiftClick());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWishMenuDrag(InventoryDragEvent e) {
|
||||
if (e.getInventory().getHolder() instanceof WishManager.Menu) e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import de.mviper.adventskalender.Adventskalender;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/** Zentrale Verwaltung der Weihnachts-Module: Schnee, Santa, Cracker, Magic Tree. */
|
||||
public final class ChristmasManager {
|
||||
private static ChristmasManager instance;
|
||||
public static final Random RANDOM = new Random();
|
||||
|
||||
private final Adventskalender plugin;
|
||||
private YamlConfiguration cfg;
|
||||
private final NamespacedKey snowOffKey;
|
||||
private BukkitTask snowTask;
|
||||
|
||||
private SantaEvent santa;
|
||||
private CrackerManager crackers;
|
||||
private MagicTreeManager trees;
|
||||
private SnowmanManager snowmen;
|
||||
private CookieManager cookies;
|
||||
private CountdownManager countdown;
|
||||
private WichtelnManager wichteln;
|
||||
private WreathManager wreaths;
|
||||
private SnowballGame snowball;
|
||||
private DecoManager deco;
|
||||
private LoginStreakManager streaks;
|
||||
private GreetingManager greetings;
|
||||
private StockingManager stockings;
|
||||
private HatManager hats;
|
||||
private QuizManager quiz;
|
||||
private WishManager wishes;
|
||||
private MarketManager market;
|
||||
private PortalManager portals;
|
||||
private YamlConfiguration msgs;
|
||||
|
||||
private ChristmasManager(Adventskalender plugin) {
|
||||
this.plugin = plugin;
|
||||
this.snowOffKey = new NamespacedKey(plugin, "snow_off");
|
||||
}
|
||||
|
||||
public static void enable(Adventskalender plugin) {
|
||||
instance = new ChristmasManager(plugin);
|
||||
instance.loadConfig();
|
||||
instance.santa = new SantaEvent(instance);
|
||||
instance.crackers = new CrackerManager(instance);
|
||||
instance.trees = new MagicTreeManager(instance);
|
||||
instance.snowmen = new SnowmanManager(instance);
|
||||
instance.loadMessages();
|
||||
instance.cookies = new CookieManager(instance);
|
||||
instance.countdown = new CountdownManager(instance);
|
||||
instance.wichteln = new WichtelnManager(instance);
|
||||
instance.wreaths = new WreathManager(instance);
|
||||
instance.snowball = new SnowballGame(instance);
|
||||
instance.deco = new DecoManager(instance);
|
||||
instance.streaks = new LoginStreakManager(instance);
|
||||
instance.greetings = new GreetingManager(instance);
|
||||
instance.stockings = new StockingManager(instance);
|
||||
instance.hats = new HatManager(instance);
|
||||
instance.quiz = new QuizManager(instance);
|
||||
instance.wishes = new WishManager(instance);
|
||||
instance.market = new MarketManager(instance);
|
||||
instance.portals = new PortalManager(instance);
|
||||
instance.crackers.reload();
|
||||
instance.trees.load();
|
||||
instance.snowmen.start();
|
||||
instance.countdown.start();
|
||||
instance.wichteln.load();
|
||||
instance.wreaths.load();
|
||||
instance.snowball.load();
|
||||
instance.streaks.load();
|
||||
instance.greetings.start();
|
||||
instance.stockings.load();
|
||||
instance.quiz.start();
|
||||
instance.wishes.load();
|
||||
instance.market.load();
|
||||
instance.portals.load();
|
||||
instance.startSnow();
|
||||
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
|
||||
try {
|
||||
new XmasExpansion(instance).register();
|
||||
} catch (Throwable t) {
|
||||
plugin.getLogger().warning("PlaceholderAPI-Platzhalter (xmas) konnten nicht registriert werden: " + t);
|
||||
}
|
||||
}
|
||||
|
||||
ChristmasCommand cmd = new ChristmasCommand(instance);
|
||||
plugin.getCommand("christmas").setExecutor(cmd);
|
||||
plugin.getCommand("christmas").setTabCompleter(cmd);
|
||||
plugin.getCommand("cracker").setExecutor(cmd);
|
||||
plugin.getCommand("cracker").setTabCompleter(cmd);
|
||||
plugin.getServer().getPluginManager().registerEvents(new ChristmasListener(instance), plugin);
|
||||
}
|
||||
|
||||
public static void disable() {
|
||||
if (instance == null) return;
|
||||
instance.santa.stop(false);
|
||||
instance.trees.save();
|
||||
instance.trees.shutdown();
|
||||
instance.snowmen.stop();
|
||||
instance.countdown.stop();
|
||||
instance.wichteln.save();
|
||||
instance.wreaths.stop();
|
||||
instance.snowball.shutdown();
|
||||
instance.streaks.save();
|
||||
instance.greetings.stop();
|
||||
instance.stockings.stop();
|
||||
instance.quiz.stop();
|
||||
instance.portals.stop();
|
||||
if (instance.snowTask != null) instance.snowTask.cancel();
|
||||
instance = null;
|
||||
}
|
||||
|
||||
public static void reload() {
|
||||
if (instance == null) return;
|
||||
instance.loadConfig();
|
||||
instance.loadMessages();
|
||||
instance.crackers.reload();
|
||||
instance.countdown.start();
|
||||
instance.quiz.start();
|
||||
instance.market.refreshAll();
|
||||
instance.startSnow();
|
||||
}
|
||||
|
||||
public static ChristmasManager get() { return instance; }
|
||||
|
||||
private void loadConfig() {
|
||||
File f = new File(plugin.getDataFolder(), "christmas.yml");
|
||||
if (!f.exists()) plugin.saveResource("christmas.yml", false);
|
||||
cfg = YamlConfiguration.loadConfiguration(f);
|
||||
try (java.io.InputStream in = plugin.getResource("christmas.yml")) {
|
||||
if (in != null) {
|
||||
cfg.setDefaults(YamlConfiguration.loadConfiguration(
|
||||
new java.io.InputStreamReader(in, java.nio.charset.StandardCharsets.UTF_8)));
|
||||
}
|
||||
} catch (java.io.IOException ignored) { }
|
||||
}
|
||||
|
||||
public Adventskalender plugin() { return plugin; }
|
||||
public YamlConfiguration cfg() { return cfg; }
|
||||
public SantaEvent santa() { return santa; }
|
||||
public CrackerManager crackers() { return crackers; }
|
||||
public MagicTreeManager trees() { return trees; }
|
||||
public SnowmanManager snowmen() { return snowmen; }
|
||||
public CookieManager cookies() { return cookies; }
|
||||
public CountdownManager countdown() { return countdown; }
|
||||
public WichtelnManager wichteln() { return wichteln; }
|
||||
public WreathManager wreaths() { return wreaths; }
|
||||
public SnowballGame snowball() { return snowball; }
|
||||
public DecoManager deco() { return deco; }
|
||||
public LoginStreakManager streaks() { return streaks; }
|
||||
public GreetingManager greetings() { return greetings; }
|
||||
public StockingManager stockings() { return stockings; }
|
||||
public HatManager hats() { return hats; }
|
||||
public QuizManager quiz() { return quiz; }
|
||||
public WishManager wishes() { return wishes; }
|
||||
public MarketManager market() { return market; }
|
||||
public PortalManager portals() { return portals; }
|
||||
|
||||
/** Baum-, Geschenk- und Kranzbloecke, die vor Zerstoerung geschuetzt sind. */
|
||||
public boolean isProtected(org.bukkit.block.Block b) {
|
||||
return trees.isProtected(b) || wreaths.isProtected(b) || portals.isProtected(b) || stockings.isProtected(b);
|
||||
}
|
||||
|
||||
// ---------- Nachrichten ----------
|
||||
|
||||
/** Laedt die Sprachdatei (christmas_messages_de.yml / _en.yml) passend zu general.language. */
|
||||
private void loadMessages() {
|
||||
String lang = plugin.getConfig().getString("general.language", "de").toLowerCase();
|
||||
if (!lang.matches("[a-z_]+")) lang = "de";
|
||||
String res = "christmas_messages_" + lang + ".yml";
|
||||
if (plugin.getResource(res) == null) res = "christmas_messages_de.yml";
|
||||
File f = new File(plugin.getDataFolder(), res);
|
||||
if (!f.exists()) plugin.saveResource(res, false);
|
||||
msgs = YamlConfiguration.loadConfiguration(f);
|
||||
try (java.io.InputStream in = plugin.getResource(res)) {
|
||||
if (in != null) {
|
||||
msgs.setDefaults(YamlConfiguration.loadConfiguration(
|
||||
new java.io.InputStreamReader(in, java.nio.charset.StandardCharsets.UTF_8)));
|
||||
}
|
||||
} catch (java.io.IOException ignored) { }
|
||||
}
|
||||
|
||||
public String raw(String key) {
|
||||
// Reihenfolge: Sprachdatei, alte Texte aus christmas.yml, sonst Fehlermeldung
|
||||
String text = msgs == null ? null : msgs.getString("messages." + key);
|
||||
if (text == null) text = cfg.getString("messages." + key);
|
||||
return ChatColor.translateAlternateColorCodes('&', text != null ? text : "&cMissing: " + key);
|
||||
}
|
||||
|
||||
public List<String> rawList(String key) {
|
||||
List<String> l = msgs == null ? List.of() : msgs.getStringList("messages." + key);
|
||||
if (l.isEmpty()) l = cfg.getStringList("messages." + key);
|
||||
List<String> out = new java.util.ArrayList<>();
|
||||
for (String x : l) out.add(ChatColor.translateAlternateColorCodes('&', x));
|
||||
return out;
|
||||
}
|
||||
|
||||
public String msg(String key, String... replacements) {
|
||||
String s = raw(key);
|
||||
for (int i = 0; i + 1 < replacements.length; i += 2) s = s.replace(replacements[i], replacements[i + 1]);
|
||||
return raw("prefix") + s;
|
||||
}
|
||||
|
||||
public String prefixed(String text) {
|
||||
return raw("prefix") + text;
|
||||
}
|
||||
|
||||
/** "300", "30s", "5m", "1h" -> Sekunden; -1 bei ungueltiger Eingabe. */
|
||||
public static int parseDuration(String s) {
|
||||
if (s == null || s.isBlank()) return -1;
|
||||
s = s.trim().toLowerCase();
|
||||
int mult = 1;
|
||||
char last = s.charAt(s.length() - 1);
|
||||
if (last == 's' || last == 'm' || last == 'h') {
|
||||
mult = last == 'm' ? 60 : last == 'h' ? 3600 : 1;
|
||||
s = s.substring(0, s.length() - 1);
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(s) * mult;
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static String color(String s) {
|
||||
return ChatColor.translateAlternateColorCodes('&', s);
|
||||
}
|
||||
|
||||
// ---------- Item-Hilfen ----------
|
||||
|
||||
/**
|
||||
* "DIAMOND:2" -> ItemStack, null wenn ungueltig. Zusaetzlich: "cracker:id:n", "cookie:id:n", "crystal:n".
|
||||
*/
|
||||
public static ItemStack parseItem(String spec) {
|
||||
if (spec == null) return null;
|
||||
String[] p = spec.trim().split(":");
|
||||
String head = p[0].trim().toLowerCase();
|
||||
ChristmasManager m = instance;
|
||||
if (m != null) {
|
||||
if ((head.equals("cracker") || head.equals("cookie")) && p.length > 1) {
|
||||
int a = p.length > 2 ? toInt(p[2], 1) : 1;
|
||||
return head.equals("cracker") ? m.crackers.create(p[1].trim(), a) : m.cookies.create(p[1].trim(), a);
|
||||
}
|
||||
if (head.equals("crystal")) return m.trees.crystal(p.length > 1 ? toInt(p[1], 1) : 1);
|
||||
if (head.equals("present")) return m.presentHead(p.length > 1 ? toInt(p[1], 1) : 1);
|
||||
}
|
||||
Material mat = Material.matchMaterial(p[0].trim());
|
||||
if (mat == null || !mat.isItem() || mat.isAir()) return null;
|
||||
return new ItemStack(mat, Math.max(1, p.length > 1 ? toInt(p[1], 1) : 1));
|
||||
}
|
||||
|
||||
/** Geschenk-Kopf (Textur aus magic-tree.presents) als Item. */
|
||||
ItemStack presentHead(int amount) {
|
||||
ItemStack it = new ItemStack(Material.PLAYER_HEAD, Math.max(1, amount));
|
||||
List<String> skins = cfg.getStringList("magic-tree.presents");
|
||||
if (skins.isEmpty()) return it;
|
||||
org.bukkit.inventory.meta.SkullMeta meta = (org.bukkit.inventory.meta.SkullMeta) it.getItemMeta();
|
||||
if (meta == null) return it;
|
||||
String url = skins.get(RANDOM.nextInt(skins.size()));
|
||||
try {
|
||||
org.bukkit.profile.PlayerProfile prof = Bukkit.createPlayerProfile(java.util.UUID.nameUUIDFromBytes(url.getBytes()), "present");
|
||||
org.bukkit.profile.PlayerTextures tex = prof.getTextures();
|
||||
tex.setSkin(new java.net.URL(url));
|
||||
prof.setTextures(tex);
|
||||
meta.setOwnerProfile(prof);
|
||||
meta.setDisplayName(raw("present-name"));
|
||||
it.setItemMeta(meta);
|
||||
} catch (java.net.MalformedURLException ignored) { }
|
||||
return it;
|
||||
}
|
||||
|
||||
static int toInt(String s, int def) {
|
||||
try { return Integer.parseInt(s.trim()); } catch (NumberFormatException e) { return def; }
|
||||
}
|
||||
|
||||
public static boolean hasAll(Player p, List<String> cost) {
|
||||
for (String c : cost) {
|
||||
ItemStack it = parseItem(c);
|
||||
if (it != null && !p.getInventory().containsAtLeast(new ItemStack(it.getType()), it.getAmount())) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void takeAll(Player p, List<String> cost) {
|
||||
for (String c : cost) {
|
||||
ItemStack it = parseItem(c);
|
||||
if (it != null) p.getInventory().removeItem(it);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Schnee ----------
|
||||
|
||||
public boolean isSnowEnabledFor(Player p) {
|
||||
Byte off = p.getPersistentDataContainer().get(snowOffKey, PersistentDataType.BYTE);
|
||||
if (off != null) return off == 0;
|
||||
return cfg.getBoolean("snow.default-on", true);
|
||||
}
|
||||
|
||||
public boolean toggleSnow(Player p) {
|
||||
boolean now = !isSnowEnabledFor(p);
|
||||
p.getPersistentDataContainer().set(snowOffKey, PersistentDataType.BYTE, (byte) (now ? 0 : 1));
|
||||
return now;
|
||||
}
|
||||
|
||||
private void startSnow() {
|
||||
if (snowTask != null) snowTask.cancel();
|
||||
snowTask = null;
|
||||
if (!cfg.getBoolean("snow.enable", true)) return;
|
||||
int interval = Math.max(1, cfg.getInt("snow.display-task-interval", 2));
|
||||
int level = Math.max(1, Math.min(5, cfg.getInt("snow.level", 3)));
|
||||
List<String> blocked = cfg.getStringList("snow.disable-in-worlds");
|
||||
boolean regionsOnly = cfg.getBoolean("snow.enable-only-in-regions", false)
|
||||
&& Bukkit.getPluginManager().getPlugin("WorldGuard") != null;
|
||||
List<String> regions = cfg.getStringList("snow.regions");
|
||||
snowTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
World w = p.getWorld();
|
||||
if (blocked.contains(w.getName()) || !isSnowEnabledFor(p)) continue;
|
||||
var loc = p.getLocation();
|
||||
if (regionsOnly && !WorldGuardHook.inAnyRegion(loc, regions)) continue;
|
||||
for (int i = 0; i < level * 4; i++) {
|
||||
double x = loc.getX() + (RANDOM.nextDouble() - 0.5) * 20;
|
||||
double z = loc.getZ() + (RANDOM.nextDouble() - 0.5) * 20;
|
||||
double y = loc.getY() + 2 + RANDOM.nextDouble() * 7;
|
||||
p.spawnParticle(Particle.SNOWFLAKE, x, y, z, 0, 0, -0.15, 0, 1);
|
||||
}
|
||||
}
|
||||
}, 20L, interval);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Plaetzchen und Lebkuchen: Essbare Items mit Traenke-Effekten. */
|
||||
public final class CookieManager {
|
||||
private final ChristmasManager mgr;
|
||||
private final NamespacedKey key;
|
||||
|
||||
CookieManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.key = new NamespacedKey(mgr.plugin(), "cookie_id");
|
||||
}
|
||||
|
||||
public Set<String> ids() {
|
||||
ConfigurationSection s = mgr.cfg().getConfigurationSection("cookies");
|
||||
return s == null ? new LinkedHashSet<>() : s.getKeys(false);
|
||||
}
|
||||
|
||||
public ItemStack create(String id, int amount) {
|
||||
ConfigurationSection s = mgr.cfg().getConfigurationSection("cookies." + id);
|
||||
if (s == null) return null;
|
||||
Material m = Material.matchMaterial(s.getString("material", "COOKIE"));
|
||||
if (m == null || !m.isItem() || !m.isEdible()) m = Material.COOKIE;
|
||||
ItemStack it = new ItemStack(m, Math.max(1, amount));
|
||||
ItemMeta meta = it.getItemMeta();
|
||||
if (meta == null) return it;
|
||||
meta.setDisplayName(ChristmasManager.color(s.getString("name", "&6" + id)));
|
||||
List<String> lore = new ArrayList<>();
|
||||
for (String l : s.getStringList("lore")) lore.add(ChristmasManager.color(l));
|
||||
meta.setLore(lore);
|
||||
meta.setEnchantmentGlintOverride(true);
|
||||
meta.getPersistentDataContainer().set(key, PersistentDataType.STRING, id);
|
||||
it.setItemMeta(meta);
|
||||
return it;
|
||||
}
|
||||
|
||||
public String idOf(ItemStack it) {
|
||||
if (it == null || !it.hasItemMeta()) return null;
|
||||
return it.getItemMeta().getPersistentDataContainer().get(key, PersistentDataType.STRING);
|
||||
}
|
||||
|
||||
/** Wendet die Effekte eines gegessenen Plaetzchens an. */
|
||||
public void eat(Player p, String id) {
|
||||
for (String spec : mgr.cfg().getStringList("cookies." + id + ".effects")) {
|
||||
String[] parts = spec.split(":");
|
||||
PotionEffectType type = effect(parts[0].trim());
|
||||
if (type == null) continue;
|
||||
int seconds = parts.length > 1 ? ChristmasManager.toInt(parts[1], 30) : 30;
|
||||
int amp = parts.length > 2 ? ChristmasManager.toInt(parts[2], 0) : 0;
|
||||
p.addPotionEffect(new PotionEffect(type, seconds * 20, Math.max(0, amp)));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static PotionEffectType effect(String name) {
|
||||
String n = name.toLowerCase();
|
||||
PotionEffectType t = Registry.EFFECT.get(NamespacedKey.minecraft(n));
|
||||
return t != null ? t : PotionEffectType.getByName(name.toUpperCase());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import de.mviper.adventskalender.Adventskalender;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.boss.BarColor;
|
||||
import org.bukkit.boss.BarStyle;
|
||||
import org.bukkit.boss.BossBar;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
/** Countdown bis Heiligabend: Nachricht beim Join, Bossbar und Platzhalter. */
|
||||
public final class CountdownManager {
|
||||
private final ChristmasManager mgr;
|
||||
private final NamespacedKey offKey;
|
||||
private BossBar bar;
|
||||
private BukkitTask task;
|
||||
|
||||
CountdownManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.offKey = new NamespacedKey(mgr.plugin(), "countdown_off");
|
||||
}
|
||||
|
||||
void start() {
|
||||
stop();
|
||||
if (!mgr.cfg().getBoolean("countdown.enable", true)) return;
|
||||
bar = Bukkit.createBossBar("", BarColor.RED, BarStyle.SEGMENTED_10);
|
||||
task = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::update, 40L, 20L * 30);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
if (bar != null) { bar.removeAll(); bar = null; }
|
||||
}
|
||||
|
||||
// ---------- Berechnung ----------
|
||||
|
||||
private LocalDate today() {
|
||||
return LocalDate.now(Adventskalender.getInstance().getZone());
|
||||
}
|
||||
|
||||
private LocalDate target() {
|
||||
String t = mgr.cfg().getString("countdown.target", "12-24");
|
||||
String[] p = t.split("-");
|
||||
int month = 12, day = 24;
|
||||
try {
|
||||
month = Integer.parseInt(p[0].trim());
|
||||
day = Integer.parseInt(p[1].trim());
|
||||
} catch (Exception ignored) { }
|
||||
LocalDate today = today();
|
||||
LocalDate thisYear;
|
||||
try {
|
||||
thisYear = LocalDate.of(today.getYear(), month, day);
|
||||
} catch (Exception e) {
|
||||
thisYear = LocalDate.of(today.getYear(), 12, 24);
|
||||
}
|
||||
return today.isAfter(thisYear) ? thisYear.plusYears(1) : thisYear;
|
||||
}
|
||||
|
||||
/** Tage bis zum Zieldatum (0 = heute). */
|
||||
public int daysLeft() {
|
||||
return (int) ChronoUnit.DAYS.between(today(), target());
|
||||
}
|
||||
|
||||
/** Liegt der Countdown im Anzeigefenster (show-days-before Tage vorher)? */
|
||||
public boolean visible() {
|
||||
return mgr.cfg().getBoolean("countdown.enable", true)
|
||||
&& daysLeft() <= mgr.cfg().getInt("countdown.show-days-before", 60);
|
||||
}
|
||||
|
||||
/** Tage, bis die Anzeige beginnt (0 = schon sichtbar). */
|
||||
public int daysUntilVisible() {
|
||||
return Math.max(0, daysLeft() - mgr.cfg().getInt("countdown.show-days-before", 60));
|
||||
}
|
||||
|
||||
public String text() {
|
||||
int d = daysLeft();
|
||||
if (d == 0) return mgr.raw("countdown-today");
|
||||
return mgr.raw(d == 1 ? "countdown-bossbar-one" : "countdown-bossbar").replace("%days%", String.valueOf(d));
|
||||
}
|
||||
|
||||
// ---------- Anzeige ----------
|
||||
|
||||
private boolean hiddenFor(Player p) {
|
||||
Byte b = p.getPersistentDataContainer().get(offKey, PersistentDataType.BYTE);
|
||||
return b != null && b == 1;
|
||||
}
|
||||
|
||||
/** Schaltet die Bossbar fuer den Spieler um; true = jetzt sichtbar. */
|
||||
public boolean toggle(Player p) {
|
||||
boolean nowHidden = !hiddenFor(p);
|
||||
p.getPersistentDataContainer().set(offKey, PersistentDataType.BYTE, (byte) (nowHidden ? 1 : 0));
|
||||
update();
|
||||
return !nowHidden;
|
||||
}
|
||||
|
||||
private void update() {
|
||||
if (bar == null) return;
|
||||
boolean show = visible() && mgr.cfg().getBoolean("countdown.bossbar", true);
|
||||
if (!show) { bar.removeAll(); return; }
|
||||
int show_days = Math.max(1, mgr.cfg().getInt("countdown.show-days-before", 60));
|
||||
bar.setTitle(text());
|
||||
bar.setProgress(Math.max(0, Math.min(1, 1.0 - daysLeft() / (double) show_days)));
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (hiddenFor(p)) bar.removePlayer(p); else bar.addPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
public void onJoin(Player p) {
|
||||
if (!visible()) return;
|
||||
if (bar != null && mgr.cfg().getBoolean("countdown.bossbar", true) && !hiddenFor(p)) bar.addPlayer(p);
|
||||
if (mgr.cfg().getBoolean("countdown.join-message", true)) {
|
||||
Bukkit.getScheduler().runTaskLater(mgr.plugin(), () -> {
|
||||
if (p.isOnline()) p.sendMessage(mgr.prefixed(text()));
|
||||
}, 60L);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import de.mviper.adventskalender.RewardUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Weihnachts-Cracker: Items, die per Rechtsklick geoeffnet werden und eine zufaellige Belohnung geben. */
|
||||
public final class CrackerManager {
|
||||
private final ChristmasManager mgr;
|
||||
private final NamespacedKey key;
|
||||
|
||||
CrackerManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.key = new NamespacedKey(mgr.plugin(), "cracker_id");
|
||||
}
|
||||
|
||||
void reload() { /* Konfiguration wird bei Bedarf direkt gelesen */ }
|
||||
|
||||
public Set<String> ids() {
|
||||
ConfigurationSection s = mgr.cfg().getConfigurationSection("crackers");
|
||||
return s == null ? new LinkedHashSet<>() : s.getKeys(false);
|
||||
}
|
||||
|
||||
public String firstId() {
|
||||
return ids().stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public ItemStack create(String id, int amount) {
|
||||
ConfigurationSection s = mgr.cfg().getConfigurationSection("crackers." + id);
|
||||
if (s == null) return null;
|
||||
Material m = Material.matchMaterial(s.getString("material", "FIREWORK_STAR"));
|
||||
if (m == null || !m.isItem()) m = Material.FIREWORK_STAR;
|
||||
ItemStack it = new ItemStack(m, Math.max(1, amount));
|
||||
ItemMeta meta = it.getItemMeta();
|
||||
if (meta == null) return it;
|
||||
meta.setDisplayName(ChristmasManager.color(s.getString("name", "&cCracker")));
|
||||
List<String> lore = new ArrayList<>();
|
||||
for (String l : s.getStringList("lore")) lore.add(ChristmasManager.color(l));
|
||||
meta.setLore(lore);
|
||||
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES);
|
||||
meta.addEnchant(Enchantment.LURE, 1, true);
|
||||
meta.getPersistentDataContainer().set(key, PersistentDataType.STRING, id);
|
||||
it.setItemMeta(meta);
|
||||
return it;
|
||||
}
|
||||
|
||||
/** Gibt die Cracker-ID des Items zurueck oder null. */
|
||||
public String idOf(ItemStack it) {
|
||||
if (it == null || !it.hasItemMeta()) return null;
|
||||
return it.getItemMeta().getPersistentDataContainer().get(key, PersistentDataType.STRING);
|
||||
}
|
||||
|
||||
/** Oeffnet einen Cracker (verbraucht das Item NICHT, das macht der Aufrufer). */
|
||||
public void open(Player p, String id) {
|
||||
List<Map<?, ?>> rewards = mgr.cfg().getMapList("crackers." + id + ".rewards");
|
||||
double sum = 0;
|
||||
for (Map<?, ?> r : rewards) sum += weight(r);
|
||||
if (sum <= 0) return;
|
||||
double roll = ChristmasManager.RANDOM.nextDouble() * sum;
|
||||
for (Map<?, ?> r : rewards) {
|
||||
roll -= weight(r);
|
||||
if (roll > 0) continue;
|
||||
ChristmasExtras.applyMap(p, r);
|
||||
break;
|
||||
}
|
||||
p.sendMessage(mgr.msg("cracker-opened"));
|
||||
p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation().add(0, 1.5, 0), 40, 0.4, 0.4, 0.4, 0.1);
|
||||
p.getWorld().playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1f, 1f);
|
||||
}
|
||||
|
||||
private static double weight(Map<?, ?> r) {
|
||||
Object c = r.get("chance");
|
||||
return c instanceof Number n ? Math.max(0, n.doubleValue()) : 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Skull;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.Candle;
|
||||
import org.bukkit.block.data.type.Leaves;
|
||||
import org.bukkit.block.data.type.Snow;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
import org.bukkit.profile.PlayerTextures;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Weihnachtsmarkt-Deko: Ein Menue, aus dem Admins Deko per Klick aufstellen und wieder rueckgaengig machen. */
|
||||
public final class DecoManager {
|
||||
|
||||
public static final class Menu implements InventoryHolder {
|
||||
private Inventory inv;
|
||||
@Override public Inventory getInventory() { return inv; }
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
PRESENT(Material.CHEST, 10), CANDY_CANE(Material.RED_CONCRETE, 11),
|
||||
LANTERN_POST(Material.LANTERN, 12), TREE(Material.SPRUCE_SAPLING, 13), WREATH(Material.RED_CANDLE, 14),
|
||||
SNOWMAN(Material.CARVED_PUMPKIN, 15), LIGHTS(Material.SEA_LANTERN, 16), SNOW_PILE(Material.SNOW_BLOCK, 21);
|
||||
final Material icon;
|
||||
final int slot;
|
||||
Type(Material icon, int slot) { this.icon = icon; this.slot = slot; }
|
||||
}
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private final Map<UUID, Deque<Runnable>> undo = new HashMap<>();
|
||||
|
||||
DecoManager(ChristmasManager mgr) { this.mgr = mgr; }
|
||||
|
||||
// ---------- Menue ----------
|
||||
|
||||
public void open(Player p) {
|
||||
Menu holder = new Menu();
|
||||
holder.inv = Bukkit.createInventory(holder, 27, mgr.raw("deco-menu-title"));
|
||||
for (Type t : Type.values()) {
|
||||
ItemStack it = new ItemStack(t.icon);
|
||||
ItemMeta meta = it.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(mgr.raw("deco-" + t.name().toLowerCase()));
|
||||
meta.setLore(List.of(mgr.raw("deco-click-hint")));
|
||||
it.setItemMeta(meta);
|
||||
}
|
||||
holder.inv.setItem(t.slot, it);
|
||||
}
|
||||
p.openInventory(holder.inv);
|
||||
}
|
||||
|
||||
public void click(Player p, int slot) {
|
||||
for (Type t : Type.values()) {
|
||||
if (t.slot == slot) {
|
||||
p.closeInventory();
|
||||
place(p, t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Platzieren ----------
|
||||
|
||||
private final class Session {
|
||||
final List<BlockState> before = new ArrayList<>();
|
||||
final List<Entity> entities = new ArrayList<>();
|
||||
|
||||
void set(Block b, BlockData data) {
|
||||
before.add(b.getState());
|
||||
b.setBlockData(data, false);
|
||||
}
|
||||
|
||||
void set(Block b, Material m) { set(b, m.createBlockData()); }
|
||||
|
||||
boolean free(Block b) {
|
||||
return b.getType().isAir() || (b.isPassable() && !b.isLiquid());
|
||||
}
|
||||
}
|
||||
|
||||
public void place(Player p, Type type) {
|
||||
Block target = p.getTargetBlockExact(8);
|
||||
if (target == null) { p.sendMessage(mgr.msg("deco-look")); return; }
|
||||
Block base = target.getRelative(BlockFace.UP);
|
||||
Session s = new Session();
|
||||
BlockFace facing = p.getFacing();
|
||||
|
||||
switch (type) {
|
||||
case PRESENT -> present(s, base);
|
||||
case CANDY_CANE -> candyCane(s, base, facing);
|
||||
case LANTERN_POST -> {
|
||||
for (int i = 0; i < 3; i++) if (s.free(base.getRelative(0, i, 0))) s.set(base.getRelative(0, i, 0), Material.OAK_FENCE);
|
||||
Block top = base.getRelative(0, 3, 0);
|
||||
if (s.free(top)) s.set(top, Material.LANTERN);
|
||||
}
|
||||
case TREE -> {
|
||||
Map<Vector, BlockData> st = mgr.trees().structure(MagicTreeManager.Level.TREE);
|
||||
for (var e : st.entrySet()) {
|
||||
Vector v = e.getKey();
|
||||
Block b = base.getRelative(v.getBlockX(), v.getBlockY(), v.getBlockZ());
|
||||
if (s.free(b)) s.set(b, e.getValue());
|
||||
}
|
||||
}
|
||||
case WREATH -> wreath(s, base);
|
||||
case SNOWMAN -> {
|
||||
mgr.snowmen().spawnDeco(base.getLocation().add(0.5, 0, 0.5), "");
|
||||
for (Entity e : base.getWorld().getNearbyEntities(base.getLocation().add(0.5, 0.5, 0.5), 1, 1, 1)) {
|
||||
if (e instanceof org.bukkit.entity.Snowman sm && mgr.snowmen().isDeco(sm)) s.entities.add(sm);
|
||||
}
|
||||
}
|
||||
case LIGHTS -> {
|
||||
Material[] mats = {Material.SEA_LANTERN, Material.SHROOMLIGHT, Material.GLOWSTONE, Material.SHROOMLIGHT};
|
||||
BlockFace f = facing == BlockFace.UP || facing == BlockFace.DOWN ? BlockFace.NORTH : facing;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
Block b = base.getRelative(f, i);
|
||||
if (s.free(b)) s.set(b, mats[i % mats.length]);
|
||||
}
|
||||
}
|
||||
case SNOW_PILE -> {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
Block b = base.getRelative(dx, 0, dz);
|
||||
if (!s.free(b)) continue;
|
||||
Snow data = (Snow) Material.SNOW.createBlockData();
|
||||
data.setLayers(dx == 0 && dz == 0 ? 3 : (Math.abs(dx) + Math.abs(dz) == 1 ? 2 : 1));
|
||||
s.set(b, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
undo.computeIfAbsent(p.getUniqueId(), k -> new ArrayDeque<>()).push(() -> {
|
||||
for (int i = s.before.size() - 1; i >= 0; i--) s.before.get(i).update(true, false);
|
||||
for (Entity e : s.entities) if (e.isValid()) e.remove();
|
||||
});
|
||||
Deque<Runnable> stack = undo.get(p.getUniqueId());
|
||||
while (stack.size() > 30) stack.removeLast();
|
||||
p.sendMessage(mgr.msg("deco-placed"));
|
||||
}
|
||||
|
||||
public void undoLast(Player p) {
|
||||
Deque<Runnable> stack = undo.get(p.getUniqueId());
|
||||
if (stack == null || stack.isEmpty()) { p.sendMessage(mgr.msg("deco-nothing-undo")); return; }
|
||||
stack.pop().run();
|
||||
p.sendMessage(mgr.msg("deco-undone"));
|
||||
}
|
||||
|
||||
// ---------- Formen ----------
|
||||
|
||||
private void present(Session s, Block base) {
|
||||
List<String> skins = mgr.cfg().getStringList("magic-tree.presents");
|
||||
if (!s.free(base) || skins.isEmpty()) return;
|
||||
s.set(base, Material.PLAYER_HEAD);
|
||||
if (base.getState() instanceof Skull sk) {
|
||||
String url = skins.get(ChristmasManager.RANDOM.nextInt(skins.size()));
|
||||
try {
|
||||
PlayerProfile prof = Bukkit.createPlayerProfile(UUID.nameUUIDFromBytes(url.getBytes()), "present");
|
||||
PlayerTextures tex = prof.getTextures();
|
||||
tex.setSkin(new URL(url));
|
||||
prof.setTextures(tex);
|
||||
sk.setOwnerProfile(prof);
|
||||
sk.update(false, false);
|
||||
} catch (MalformedURLException ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
private void candyCane(Session s, Block base, BlockFace facing) {
|
||||
BlockFace f = facing == BlockFace.UP || facing == BlockFace.DOWN ? BlockFace.NORTH : facing;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Block b = base.getRelative(0, i, 0);
|
||||
if (s.free(b)) s.set(b, i % 2 == 0 ? Material.RED_CONCRETE : Material.WHITE_CONCRETE);
|
||||
}
|
||||
Block hook = base.getRelative(0, 3, 0).getRelative(f);
|
||||
if (s.free(hook)) s.set(hook, Material.RED_CONCRETE);
|
||||
Block hook2 = hook.getRelative(BlockFace.DOWN);
|
||||
if (s.free(hook2)) s.set(hook2, Material.WHITE_CONCRETE);
|
||||
}
|
||||
|
||||
private void wreath(Session s, Block base) {
|
||||
Leaves leaves = (Leaves) Material.SPRUCE_LEAVES.createBlockData();
|
||||
leaves.setPersistent(true);
|
||||
int[][] ring = {{-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1}};
|
||||
for (int[] r : ring) {
|
||||
Block b = base.getRelative(r[0], 0, r[1]);
|
||||
if (s.free(b)) s.set(b, leaves);
|
||||
}
|
||||
int[][] candles = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};
|
||||
for (int[] c : candles) {
|
||||
Block b = base.getRelative(c[0], 1, c[1]);
|
||||
if (!s.free(b)) continue;
|
||||
Candle data = (Candle) Material.RED_CANDLE.createBlockData();
|
||||
data.setLit(true);
|
||||
s.set(b, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.FireworkEffect;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Firework;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.meta.FireworkMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/** Weihnachts- und Neujahrsgruss: Chat-Nachricht an bestimmten Tagen, auf Wunsch mit Feuerwerk beim Join. */
|
||||
public final class GreetingManager {
|
||||
private final ChristmasManager mgr;
|
||||
private final File file;
|
||||
private final NamespacedKey fireworkKey;
|
||||
private String lastDate = "";
|
||||
private BukkitTask task;
|
||||
|
||||
GreetingManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.file = new File(mgr.plugin().getDataFolder(), "greetings.yml");
|
||||
this.fireworkKey = new NamespacedKey(mgr.plugin(), "xmas_firework");
|
||||
}
|
||||
|
||||
void start() {
|
||||
stop();
|
||||
if (file.exists()) lastDate = YamlConfiguration.loadConfiguration(file).getString("last", "");
|
||||
task = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::check, 200L, 20L * 30);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
}
|
||||
|
||||
private LocalDate today() { return LocalDate.now(mgr.plugin().getZone()); }
|
||||
|
||||
/** Nachrichten-Schluessel fuer heute oder null. */
|
||||
private String keyForToday() {
|
||||
if (!mgr.cfg().getBoolean("greetings.enable", true)) return null;
|
||||
LocalDate d = today();
|
||||
String md = String.format("%02d-%02d", d.getMonthValue(), d.getDayOfMonth());
|
||||
return mgr.cfg().getString("greetings.days." + md);
|
||||
}
|
||||
|
||||
/** Einmal pro Tag an alle Online-Spieler. */
|
||||
private void check() {
|
||||
String key = keyForToday();
|
||||
if (key == null || today().toString().equals(lastDate)) return;
|
||||
if (Bukkit.getOnlinePlayers().isEmpty()) return;
|
||||
lastDate = today().toString();
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
y.set("last", lastDate);
|
||||
try { y.save(file); } catch (IOException ignored) { }
|
||||
|
||||
Bukkit.broadcastMessage(mgr.prefixed(mgr.raw(key)));
|
||||
if (mgr.cfg().getBoolean("greetings.fireworks-on-broadcast", true)) {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) fireworks(p);
|
||||
}
|
||||
}
|
||||
|
||||
public void onJoin(Player p) {
|
||||
String key = keyForToday();
|
||||
if (key == null) return;
|
||||
Bukkit.getScheduler().runTaskLater(mgr.plugin(), () -> {
|
||||
if (!p.isOnline()) return;
|
||||
p.sendMessage(mgr.prefixed(mgr.raw(key)));
|
||||
if (mgr.cfg().getBoolean("greetings.join-fireworks", true)) fireworks(p);
|
||||
}, 80L);
|
||||
}
|
||||
|
||||
/** Ein kleines Feuerwerk hoch ueber dem Spieler (kein Schaden). */
|
||||
public void fireworks(Player p) {
|
||||
Location at = p.getLocation().add(0, 8, 0);
|
||||
Firework fw = p.getWorld().spawn(at, Firework.class, f -> {
|
||||
FireworkMeta meta = f.getFireworkMeta();
|
||||
meta.addEffect(FireworkEffect.builder().with(FireworkEffect.Type.BALL_LARGE)
|
||||
.withColor(Color.RED, Color.WHITE).withFade(Color.GREEN).flicker(true).trail(true).build());
|
||||
f.setFireworkMeta(meta);
|
||||
f.getPersistentDataContainer().set(fireworkKey, PersistentDataType.BYTE, (byte) 1);
|
||||
});
|
||||
fw.detonate();
|
||||
}
|
||||
|
||||
public boolean isOurs(Entity e) {
|
||||
return e.getPersistentDataContainer().has(fireworkKey, PersistentDataType.BYTE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
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.Color;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.LeatherArmorMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/** Nikolausmuetze: Rotes Kopf-Item, das beim Join angeboten (oder direkt aufgesetzt) wird. */
|
||||
public final class HatManager {
|
||||
private final ChristmasManager mgr;
|
||||
private final NamespacedKey hatKey;
|
||||
|
||||
HatManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.hatKey = new NamespacedKey(mgr.plugin(), "santa_hat");
|
||||
}
|
||||
|
||||
public boolean inSeason() {
|
||||
if (!mgr.cfg().getBoolean("santa-hat.enable", true)) return false;
|
||||
LocalDate today = LocalDate.now(mgr.plugin().getZone());
|
||||
try {
|
||||
String[] from = mgr.cfg().getString("santa-hat.from", "12-01").split("-");
|
||||
String[] until = mgr.cfg().getString("santa-hat.until", "12-31").split("-");
|
||||
LocalDate a = LocalDate.of(today.getYear(), Integer.parseInt(from[0].trim()), Integer.parseInt(from[1].trim()));
|
||||
LocalDate b = LocalDate.of(today.getYear(), Integer.parseInt(until[0].trim()), Integer.parseInt(until[1].trim()));
|
||||
return !today.isBefore(a) && !today.isAfter(b);
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public ItemStack create() {
|
||||
ItemStack it = new ItemStack(Material.LEATHER_HELMET);
|
||||
LeatherArmorMeta meta = (LeatherArmorMeta) it.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setColor(Color.fromRGB(200, 25, 30));
|
||||
meta.setDisplayName(mgr.raw("hat-name"));
|
||||
meta.setLore(mgr.rawList("hat-lore"));
|
||||
meta.setUnbreakable(true);
|
||||
meta.addItemFlags(org.bukkit.inventory.ItemFlag.HIDE_UNBREAKABLE, org.bukkit.inventory.ItemFlag.HIDE_ATTRIBUTES,
|
||||
org.bukkit.inventory.ItemFlag.HIDE_DYE);
|
||||
meta.getPersistentDataContainer().set(hatKey, PersistentDataType.BYTE, (byte) 1);
|
||||
it.setItemMeta(meta);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
private boolean isHat(ItemStack it) {
|
||||
return it != null && it.hasItemMeta()
|
||||
&& it.getItemMeta().getPersistentDataContainer().has(hatKey, PersistentDataType.BYTE);
|
||||
}
|
||||
|
||||
private boolean owns(Player p) {
|
||||
for (ItemStack it : p.getInventory().getContents()) if (isHat(it)) return true;
|
||||
for (ItemStack it : p.getInventory().getArmorContents()) if (isHat(it)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** /xmas hat und Klick auf das Angebot. */
|
||||
public void give(Player p) {
|
||||
if (!inSeason()) { p.sendMessage(mgr.msg("hat-season")); return; }
|
||||
if (owns(p)) { p.sendMessage(mgr.msg("hat-have")); return; }
|
||||
ItemStack hat = create();
|
||||
if (p.getInventory().getHelmet() == null || p.getInventory().getHelmet().getType().isAir()) {
|
||||
p.getInventory().setHelmet(hat);
|
||||
} else {
|
||||
de.mviper.adventskalender.RewardUtil.give(p, hat);
|
||||
}
|
||||
p.sendMessage(mgr.msg("hat-given"));
|
||||
}
|
||||
|
||||
public void onJoin(Player p) {
|
||||
if (!inSeason() || owns(p)) return;
|
||||
Bukkit.getScheduler().runTaskLater(mgr.plugin(), () -> {
|
||||
if (!p.isOnline() || owns(p)) return;
|
||||
if (mgr.cfg().getBoolean("santa-hat.auto-equip", false)) {
|
||||
give(p);
|
||||
return;
|
||||
}
|
||||
if (!mgr.cfg().getBoolean("santa-hat.offer-on-join", true)) return;
|
||||
TextComponent msg = new TextComponent(mgr.prefixed(mgr.raw("hat-offer")) + " ");
|
||||
TextComponent click = new TextComponent(mgr.raw("hat-offer-click"));
|
||||
click.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/xmas hat"));
|
||||
click.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(mgr.raw("hat-offer-hover"))));
|
||||
msg.addExtra(click);
|
||||
p.spigot().sendMessage(msg);
|
||||
}, 120L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Login-Serie: Wer mehrere Tage hintereinander online kommt, bekommt steigende Belohnungen. */
|
||||
public final class LoginStreakManager {
|
||||
private static final class Data {
|
||||
LocalDate last;
|
||||
int streak;
|
||||
int best;
|
||||
}
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private final File file;
|
||||
private final Map<UUID, Data> data = new HashMap<>();
|
||||
|
||||
LoginStreakManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.file = new File(mgr.plugin().getDataFolder(), "streaks.yml");
|
||||
}
|
||||
|
||||
public int streak(UUID id) {
|
||||
Data d = data.get(id);
|
||||
return d == null ? 0 : d.streak;
|
||||
}
|
||||
|
||||
public int best(UUID id) {
|
||||
Data d = data.get(id);
|
||||
return d == null ? 0 : d.best;
|
||||
}
|
||||
|
||||
void load() {
|
||||
data.clear();
|
||||
if (!file.exists()) return;
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(file);
|
||||
ConfigurationSection s = y.getConfigurationSection("players");
|
||||
if (s == null) return;
|
||||
for (String k : s.getKeys(false)) {
|
||||
try {
|
||||
Data d = new Data();
|
||||
String last = s.getString(k + ".last");
|
||||
d.last = last == null ? null : LocalDate.parse(last);
|
||||
d.streak = s.getInt(k + ".streak");
|
||||
d.best = s.getInt(k + ".best");
|
||||
data.put(UUID.fromString(k), d);
|
||||
} catch (Exception ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
void save() {
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
for (var e : data.entrySet()) {
|
||||
String p = "players." + e.getKey();
|
||||
y.set(p + ".last", e.getValue().last == null ? null : e.getValue().last.toString());
|
||||
y.set(p + ".streak", e.getValue().streak);
|
||||
y.set(p + ".best", e.getValue().best);
|
||||
}
|
||||
try {
|
||||
y.save(file);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("streaks.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean inWindow(LocalDate today) {
|
||||
try {
|
||||
String[] from = mgr.cfg().getString("login-streak.from", "12-01").split("-");
|
||||
String[] until = mgr.cfg().getString("login-streak.until", "12-31").split("-");
|
||||
LocalDate a = LocalDate.of(today.getYear(), Integer.parseInt(from[0].trim()), Integer.parseInt(from[1].trim()));
|
||||
LocalDate b = LocalDate.of(today.getYear(), Integer.parseInt(until[0].trim()), Integer.parseInt(until[1].trim()));
|
||||
return !today.isBefore(a) && !today.isAfter(b);
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void onJoin(Player p) {
|
||||
if (!mgr.cfg().getBoolean("login-streak.enable", true)) return;
|
||||
LocalDate today = LocalDate.now(mgr.plugin().getZone());
|
||||
if (!inWindow(today)) return;
|
||||
|
||||
Data d = data.computeIfAbsent(p.getUniqueId(), k -> new Data());
|
||||
if (today.equals(d.last)) return; // heute schon belohnt
|
||||
d.streak = (d.last != null && d.last.plusDays(1).equals(today)) ? d.streak + 1 : 1;
|
||||
d.best = Math.max(d.best, d.streak);
|
||||
d.last = today;
|
||||
save();
|
||||
|
||||
int cycle = Math.max(1, mgr.cfg().getInt("login-streak.cycle", 7));
|
||||
int day = ((d.streak - 1) % cycle) + 1;
|
||||
Bukkit.getScheduler().runTaskLater(mgr.plugin(), () -> {
|
||||
if (!p.isOnline()) return;
|
||||
for (Map<?, ?> reward : mgr.cfg().getMapList("login-streak.rewards." + day)) {
|
||||
ChristmasExtras.applyMap(p, reward);
|
||||
}
|
||||
p.sendMessage(mgr.msg("streak-reward", "%day%", String.valueOf(d.streak), "%cycle%", String.valueOf(day)));
|
||||
}, 100L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,812 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import de.mviper.adventskalender.RewardUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Skull;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.Leaves;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.ShapedRecipe;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
import org.bukkit.profile.PlayerTextures;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Magic Tree: Kristall auf Setzling, Items neben den Baum werfen, Rechtsklick = wachsen, Geschenk-Koepfe. */
|
||||
public final class MagicTreeManager {
|
||||
|
||||
public enum Level {
|
||||
SAPLING("sapling", 1), SMALL_TREE("small_tree", 2), TREE("tree", 3), MAGIC_TREE("magic_tree", 4);
|
||||
final String key;
|
||||
final int radius;
|
||||
Level(String key, int radius) { this.key = key; this.radius = radius; }
|
||||
Level next() { return ordinal() + 1 < values().length ? values()[ordinal() + 1] : null; }
|
||||
static Level parse(String s) {
|
||||
try { return valueOf(s.toUpperCase()); } catch (Exception e) { return SAPLING; }
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Tree {
|
||||
final UUID id = UUID.randomUUID();
|
||||
final UUID owner;
|
||||
String ownerName;
|
||||
final String world;
|
||||
final int x, y, z;
|
||||
Level level = Level.SAPLING;
|
||||
final Map<Material, Integer> req = new EnumMap<>(Material.class);
|
||||
final Map<Material, Integer> spent = new EnumMap<>(Material.class);
|
||||
final Set<String> blocks = new HashSet<>();
|
||||
final Set<String> presents = new HashSet<>();
|
||||
boolean indexed;
|
||||
boolean readyAnnounced;
|
||||
long counter;
|
||||
|
||||
Tree(UUID owner, String ownerName, String world, int x, int y, int z) {
|
||||
this.owner = owner; this.ownerName = ownerName; this.world = world;
|
||||
this.x = x; this.y = y; this.z = z;
|
||||
}
|
||||
|
||||
public UUID owner() { return owner; }
|
||||
public String ownerName() { return ownerName; }
|
||||
public Level level() { return level; }
|
||||
Location loc() {
|
||||
World w = Bukkit.getWorld(world);
|
||||
return w == null ? null : new Location(w, x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private final List<Tree> trees = new ArrayList<>();
|
||||
private final Map<String, Tree> blockMap = new HashMap<>();
|
||||
private final Map<String, Tree> presentMap = new HashMap<>();
|
||||
private final Map<UUID, Long> destroyers = new HashMap<>();
|
||||
private final File file;
|
||||
private final File giftFile;
|
||||
private final NamespacedKey crystalKey;
|
||||
private final List<ItemStack> customGifts = new ArrayList<>();
|
||||
private boolean endedFlag;
|
||||
private boolean endAnnounced;
|
||||
private BukkitTask task;
|
||||
private BukkitTask fxTask;
|
||||
private int fxTick;
|
||||
|
||||
MagicTreeManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.file = new File(mgr.plugin().getDataFolder(), "trees.yml");
|
||||
this.giftFile = new File(mgr.plugin().getDataFolder(), "gifts.yml");
|
||||
this.crystalKey = new NamespacedKey(mgr.plugin(), "xmas_crystal");
|
||||
}
|
||||
|
||||
// ================= Status =================
|
||||
|
||||
public boolean enabled() { return mgr.cfg().getBoolean("magic-tree.enable", true); }
|
||||
public int treeCount() { return trees.size(); }
|
||||
|
||||
public long countOf(UUID owner) {
|
||||
return trees.stream().filter(t -> t.owner.equals(owner)).count();
|
||||
}
|
||||
|
||||
public boolean isEnded() {
|
||||
if (endedFlag) return true;
|
||||
if (!mgr.cfg().getBoolean("magic-tree.holiday-ends.enabled", true)) return false;
|
||||
try {
|
||||
LocalDateTime end = LocalDateTime.parse(mgr.cfg().getString("magic-tree.holiday-ends.date", ""),
|
||||
DateTimeFormatter.ofPattern("dd-MM-yyyy HH-mm-ss"));
|
||||
return LocalDateTime.now().isAfter(end);
|
||||
} catch (DateTimeParseException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setEnded(boolean ended) {
|
||||
endedFlag = ended;
|
||||
save();
|
||||
if (ended) announceEnd();
|
||||
}
|
||||
|
||||
private void announceEnd() {
|
||||
if (endAnnounced) return;
|
||||
endAnnounced = true;
|
||||
Bukkit.broadcastMessage(mgr.prefixed(mgr.raw("timeout")));
|
||||
}
|
||||
|
||||
// ================= Keys =================
|
||||
|
||||
private static String key(Block b) {
|
||||
return b.getWorld().getName() + ";" + b.getX() + ";" + b.getY() + ";" + b.getZ();
|
||||
}
|
||||
|
||||
public boolean isProtected(Block b) {
|
||||
String k = key(b);
|
||||
return blockMap.containsKey(k) || presentMap.containsKey(k);
|
||||
}
|
||||
|
||||
public boolean isTreeBlock(Block b) { return blockMap.containsKey(key(b)); }
|
||||
public boolean isPresentBlock(Block b) { return presentMap.containsKey(key(b)); }
|
||||
public Tree treeOf(Block b) { return blockMap.get(key(b)); }
|
||||
public Tree treeOfPresent(Block b) { return presentMap.get(key(b)); }
|
||||
|
||||
// ================= Kristall =================
|
||||
|
||||
public ItemStack crystal(int amount) {
|
||||
ConfigurationSection s = mgr.cfg().getConfigurationSection("crystal");
|
||||
Material m = Material.matchMaterial(s == null ? "NETHER_STAR" : s.getString("material", "NETHER_STAR"));
|
||||
if (m == null || !m.isItem()) m = Material.NETHER_STAR;
|
||||
ItemStack it = new ItemStack(m, Math.max(1, amount));
|
||||
ItemMeta meta = it.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(ChristmasManager.color(s == null ? "&bWeihnachts-Kristall" : s.getString("name", "&bWeihnachts-Kristall")));
|
||||
List<String> lore = new ArrayList<>();
|
||||
if (s != null) for (String l : s.getStringList("lore")) lore.add(ChristmasManager.color(l));
|
||||
meta.setLore(lore);
|
||||
meta.getPersistentDataContainer().set(crystalKey, PersistentDataType.BYTE, (byte) 1);
|
||||
it.setItemMeta(meta);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
public boolean isCrystal(ItemStack it) {
|
||||
return it != null && it.hasItemMeta()
|
||||
&& it.getItemMeta().getPersistentDataContainer().has(crystalKey, PersistentDataType.BYTE);
|
||||
}
|
||||
|
||||
// ================= Persistenz =================
|
||||
|
||||
void load() {
|
||||
trees.clear();
|
||||
blockMap.clear();
|
||||
presentMap.clear();
|
||||
customGifts.clear();
|
||||
|
||||
if (giftFile.exists()) {
|
||||
YamlConfiguration g = YamlConfiguration.loadConfiguration(giftFile);
|
||||
for (Object o : g.getList("items", new ArrayList<>())) if (o instanceof ItemStack i) customGifts.add(i);
|
||||
}
|
||||
|
||||
if (file.exists()) {
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(file);
|
||||
endedFlag = y.getBoolean("ended", false);
|
||||
ConfigurationSection s = y.getConfigurationSection("trees");
|
||||
if (s != null) {
|
||||
for (String k : s.getKeys(false)) {
|
||||
ConfigurationSection t = s.getConfigurationSection(k);
|
||||
if (t == null || !t.isString("level")) continue;
|
||||
try {
|
||||
Tree tr = new Tree(UUID.fromString(t.getString("owner", "")), t.getString("owner-name", "?"),
|
||||
t.getString("world", "world"), t.getInt("x"), t.getInt("y"), t.getInt("z"));
|
||||
tr.level = Level.parse(t.getString("level", "SAPLING"));
|
||||
readMap(t.getConfigurationSection("req"), tr.req);
|
||||
readMap(t.getConfigurationSection("spent"), tr.spent);
|
||||
tr.counter = t.getLong("counter", 0);
|
||||
for (String p : t.getStringList("presents")) {
|
||||
tr.presents.add(p);
|
||||
presentMap.put(p, tr);
|
||||
}
|
||||
trees.add(tr);
|
||||
} catch (IllegalArgumentException ignored) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (task != null) task.cancel();
|
||||
task = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::tick, 100L, 20L);
|
||||
registerRecipe();
|
||||
if (fxTask != null) fxTask.cancel();
|
||||
fxTask = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::fx, 60L, 3L);
|
||||
}
|
||||
|
||||
// ---------- Rezept: Diamanten im Kreuz, Smaragd in der Mitte ----------
|
||||
|
||||
private NamespacedKey recipeKey() { return new NamespacedKey(mgr.plugin(), "xmas_crystal"); }
|
||||
|
||||
void registerRecipe() {
|
||||
unregisterRecipe();
|
||||
if (!mgr.cfg().getBoolean("magic-tree.crystal-recipe", true)) return;
|
||||
ShapedRecipe r = new ShapedRecipe(recipeKey(), crystal(1));
|
||||
r.shape(" D ", "DED", " D ");
|
||||
r.setIngredient('D', Material.DIAMOND);
|
||||
r.setIngredient('E', Material.EMERALD);
|
||||
Bukkit.addRecipe(r);
|
||||
}
|
||||
|
||||
void unregisterRecipe() {
|
||||
Bukkit.removeRecipe(recipeKey());
|
||||
}
|
||||
|
||||
private static void readMap(ConfigurationSection s, Map<Material, Integer> out) {
|
||||
if (s == null) return;
|
||||
for (String k : s.getKeys(false)) {
|
||||
Material m = Material.matchMaterial(k);
|
||||
if (m != null && s.getInt(k) > 0) out.put(m, s.getInt(k));
|
||||
}
|
||||
}
|
||||
|
||||
void save() {
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
y.set("ended", endedFlag);
|
||||
for (Tree t : trees) {
|
||||
String p = "trees." + t.id;
|
||||
y.set(p + ".owner", t.owner.toString());
|
||||
y.set(p + ".owner-name", t.ownerName);
|
||||
y.set(p + ".world", t.world);
|
||||
y.set(p + ".x", t.x);
|
||||
y.set(p + ".y", t.y);
|
||||
y.set(p + ".z", t.z);
|
||||
y.set(p + ".level", t.level.name());
|
||||
y.set(p + ".counter", t.counter);
|
||||
for (var e : t.req.entrySet()) y.set(p + ".req." + e.getKey().name(), e.getValue());
|
||||
for (var e : t.spent.entrySet()) y.set(p + ".spent." + e.getKey().name(), e.getValue());
|
||||
y.set(p + ".presents", new ArrayList<>(t.presents));
|
||||
}
|
||||
try {
|
||||
y.save(file);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("trees.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveGifts() {
|
||||
YamlConfiguration g = new YamlConfiguration();
|
||||
g.set("items", customGifts);
|
||||
try {
|
||||
g.save(giftFile);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("gifts.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
if (fxTask != null) fxTask.cancel();
|
||||
fxTask = null;
|
||||
unregisterRecipe();
|
||||
}
|
||||
|
||||
public boolean addGift(ItemStack hand) {
|
||||
if (hand == null || hand.getType().isAir()) return false;
|
||||
customGifts.add(hand.clone());
|
||||
saveGifts();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ================= Struktur =================
|
||||
|
||||
Map<Vector, BlockData> structure(Level lv) {
|
||||
Map<Vector, BlockData> m = new LinkedHashMap<>();
|
||||
if (lv == Level.SAPLING) {
|
||||
m.put(new Vector(0, 0, 0), Material.SPRUCE_SAPLING.createBlockData());
|
||||
return m;
|
||||
}
|
||||
int trunk;
|
||||
int[] radii;
|
||||
switch (lv) {
|
||||
case SMALL_TREE -> { trunk = 2; radii = new int[]{2, 1, 0}; }
|
||||
case TREE -> { trunk = 3; radii = new int[]{3, 2, 2, 1, 1, 0}; }
|
||||
default -> { trunk = 3; radii = new int[]{4, 3, 3, 2, 2, 1, 1, 0}; }
|
||||
}
|
||||
Leaves leaves = (Leaves) Material.SPRUCE_LEAVES.createBlockData();
|
||||
leaves.setPersistent(true);
|
||||
Material[] swag = {Material.RED_CONCRETE, Material.GOLD_BLOCK, Material.SEA_LANTERN, Material.BLUE_CONCRETE};
|
||||
|
||||
for (int y = 0; y < trunk; y++) m.put(new Vector(0, y, 0), Material.SPRUCE_LOG.createBlockData());
|
||||
for (int i = 0; i < radii.length; i++) {
|
||||
int r = radii[i];
|
||||
int y = trunk + i;
|
||||
if (r == 0) {
|
||||
m.put(new Vector(0, y, 0), Material.GLOWSTONE.createBlockData());
|
||||
continue;
|
||||
}
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
for (int dz = -r; dz <= r; dz++) {
|
||||
int d2 = dx * dx + dz * dz;
|
||||
if (d2 > r * r + 1) continue;
|
||||
if (dx == 0 && dz == 0) { m.put(new Vector(0, y, 0), Material.SPRUCE_LOG.createBlockData()); continue; }
|
||||
boolean edge = d2 >= r * r;
|
||||
if (lv != Level.SMALL_TREE && edge && Math.floorMod(dx * 7 + dz * 13 + y * 5, 5) == 0) {
|
||||
m.put(new Vector(dx, y, dz), swag[Math.floorMod(dx + dz + y, swag.length)].createBlockData());
|
||||
} else {
|
||||
m.put(new Vector(dx, y, dz), leaves);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
private boolean canGrow(Tree t, Level lv) {
|
||||
Location base = t.loc();
|
||||
if (base == null) return false;
|
||||
for (Vector v : structure(lv).keySet()) {
|
||||
Block b = base.getWorld().getBlockAt(t.x + v.getBlockX(), t.y + v.getBlockY(), t.z + v.getBlockZ());
|
||||
if (b.getType().isAir() || t.blocks.contains(key(b)) || (b.isPassable() && !b.isLiquid())) continue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void clearBlocks(Tree t) {
|
||||
World w = Bukkit.getWorld(t.world);
|
||||
for (String k : t.blocks) {
|
||||
blockMap.remove(k);
|
||||
if (w == null) continue;
|
||||
String[] p = k.split(";");
|
||||
w.getBlockAt(Integer.parseInt(p[1]), Integer.parseInt(p[2]), Integer.parseInt(p[3])).setType(Material.AIR, false);
|
||||
}
|
||||
t.blocks.clear();
|
||||
}
|
||||
|
||||
private void build(Tree t) {
|
||||
World w = Bukkit.getWorld(t.world);
|
||||
if (w == null) return;
|
||||
clearBlocks(t);
|
||||
for (var e : structure(t.level).entrySet()) {
|
||||
Vector v = e.getKey();
|
||||
Block b = w.getBlockAt(t.x + v.getBlockX(), t.y + v.getBlockY(), t.z + v.getBlockZ());
|
||||
b.setBlockData(e.getValue(), false);
|
||||
t.blocks.add(key(b));
|
||||
blockMap.put(key(b), t);
|
||||
}
|
||||
t.indexed = true;
|
||||
}
|
||||
|
||||
/** Merkt sich nach dem Laden, welche Bloecke zum Baum gehoeren (ohne etwas zu platzieren). */
|
||||
private void index(Tree t) {
|
||||
World w = Bukkit.getWorld(t.world);
|
||||
if (w == null || !w.isChunkLoaded(t.x >> 4, t.z >> 4)) return;
|
||||
t.blocks.clear();
|
||||
for (var e : structure(t.level).entrySet()) {
|
||||
Vector v = e.getKey();
|
||||
Block b = w.getBlockAt(t.x + v.getBlockX(), t.y + v.getBlockY(), t.z + v.getBlockZ());
|
||||
if (b.getType() == e.getValue().getMaterial()) {
|
||||
t.blocks.add(key(b));
|
||||
blockMap.put(key(b), t);
|
||||
}
|
||||
}
|
||||
t.indexed = true;
|
||||
}
|
||||
|
||||
private int height(Level lv) {
|
||||
int max = 0;
|
||||
for (Vector v : structure(lv).keySet()) max = Math.max(max, v.getBlockY());
|
||||
return max;
|
||||
}
|
||||
|
||||
// ================= Aktionen =================
|
||||
|
||||
private void loadRequirements(Tree t) {
|
||||
t.req.clear();
|
||||
ConfigurationSection s = mgr.cfg().getConfigurationSection("magic-tree.levels." + t.level.key + ".lvlup");
|
||||
if (s == null) return;
|
||||
for (String k : s.getKeys(false)) {
|
||||
Material m = Material.matchMaterial(k);
|
||||
if (m != null && s.getInt(k) > 0) t.req.put(m, s.getInt(k));
|
||||
}
|
||||
t.readyAnnounced = false;
|
||||
}
|
||||
|
||||
public void plant(Player p, Block sapling) {
|
||||
if (!enabled() || isEnded()) { p.sendMessage(mgr.msg("tree.tree-disabled")); return; }
|
||||
int limit = mgr.cfg().getInt("magic-tree.tree-limit", 3);
|
||||
if (!p.hasPermission("xmas.admin") && trees.stream().filter(t -> t.owner.equals(p.getUniqueId())).count() >= limit) {
|
||||
p.sendMessage(mgr.msg("tree.tree-limit"));
|
||||
return;
|
||||
}
|
||||
Tree t = new Tree(p.getUniqueId(), p.getName(), sapling.getWorld().getName(),
|
||||
sapling.getX(), sapling.getY(), sapling.getZ());
|
||||
loadRequirements(t);
|
||||
t.counter = cooldown(t.level);
|
||||
trees.add(t);
|
||||
build(t);
|
||||
save();
|
||||
p.getWorld().spawnParticle(Particle.END_ROD, sapling.getLocation().add(0.5, 0.5, 0.5), 30, 0.4, 0.4, 0.4, 0.05);
|
||||
p.playSound(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 1f, 1.2f);
|
||||
p.sendMessage(mgr.msg("tree.tree-planted"));
|
||||
}
|
||||
|
||||
private String reqText(Tree t) {
|
||||
List<String> parts = new ArrayList<>();
|
||||
for (var e : t.req.entrySet()) parts.add(e.getValue() + "x " + e.getKey().name());
|
||||
return String.join(", ", parts);
|
||||
}
|
||||
|
||||
/** Rechtsklick auf einen Baumblock. */
|
||||
public void click(Player p, Tree t) {
|
||||
if (!t.owner.equals(p.getUniqueId()) && !p.hasPermission("xmas.admin")) {
|
||||
p.sendMessage(mgr.msg("tree.not-owner"));
|
||||
return;
|
||||
}
|
||||
if (isEnded()) {
|
||||
p.sendMessage(mgr.msg("tree.tree-disabled"));
|
||||
return;
|
||||
}
|
||||
Level next = t.level.next();
|
||||
if (next == null) { p.sendMessage(mgr.msg("tree.grow-lvl-max")); return; }
|
||||
if (!t.req.isEmpty()) {
|
||||
p.sendMessage(mgr.raw("tree.grow-req-list-title"));
|
||||
p.sendMessage("§f" + reqText(t));
|
||||
return;
|
||||
}
|
||||
if (!canGrow(t, next)) { p.sendMessage(mgr.msg("tree.grow-not-enough-place")); return; }
|
||||
t.level = next;
|
||||
build(t);
|
||||
loadRequirements(t);
|
||||
t.counter = cooldown(t.level);
|
||||
save();
|
||||
Location top = t.loc().add(0.5, height(t.level) + 1, 0.5);
|
||||
t.loc().getWorld().spawnParticle(Particle.END_ROD, top, 60, 1, 1, 1, 0.1);
|
||||
t.loc().getWorld().playSound(top, Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f);
|
||||
if (t.level.next() == null) p.sendMessage(mgr.msg("tree.grow-lvl-max"));
|
||||
}
|
||||
|
||||
/** Geworfenes Item nahe am Baum aufnehmen. */
|
||||
public void absorb(Player p, Item item) {
|
||||
ItemStack stack = item.getItemStack();
|
||||
for (Tree t : trees) {
|
||||
if (!t.owner.equals(p.getUniqueId())) continue;
|
||||
Location base = t.loc();
|
||||
if (base == null || !base.getWorld().equals(item.getWorld())) continue;
|
||||
Location il = item.getLocation();
|
||||
double dx = il.getX() - (t.x + 0.5), dz = il.getZ() - (t.z + 0.5), dy = il.getY() - t.y;
|
||||
if (dx * dx + dz * dz > Math.pow(t.level.radius + 3, 2) || dy < -2 || dy > height(t.level) + 4) continue;
|
||||
Integer need = t.req.get(stack.getType());
|
||||
if (need == null || need <= 0) continue;
|
||||
|
||||
int take = Math.min(need, stack.getAmount());
|
||||
if (take >= need) t.req.remove(stack.getType()); else t.req.put(stack.getType(), need - take);
|
||||
t.spent.merge(stack.getType(), take, Integer::sum);
|
||||
if (stack.getAmount() - take <= 0) item.remove();
|
||||
else { stack.setAmount(stack.getAmount() - take); item.setItemStack(stack); }
|
||||
|
||||
il.getWorld().spawnParticle(Particle.END_ROD, il, 12, 0.2, 0.2, 0.2, 0.05);
|
||||
p.sendMessage(mgr.msg("tree.grow-fed", "%amount%", String.valueOf(take), "%item%", stack.getType().name()));
|
||||
if (t.req.isEmpty() && t.level.next() != null && !t.readyAnnounced) {
|
||||
t.readyAnnounced = true;
|
||||
p.sendMessage(mgr.msg("tree.grow-lvl-ready"));
|
||||
}
|
||||
save();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/** Abbauversuch auf einen Baumblock. */
|
||||
public void breakAttempt(Player p, Tree t, Block b) {
|
||||
boolean trunkLike = b.getType() == Material.SPRUCE_LOG || Tag_isSapling(b.getType());
|
||||
if (!trunkLike) {
|
||||
p.sendMessage(mgr.msg(t.owner.equals(p.getUniqueId()) ? "tree.destroy-leaves-tut" : "tree.destroy-leaves-santa"));
|
||||
return;
|
||||
}
|
||||
if (!t.owner.equals(p.getUniqueId()) && !p.hasPermission("xmas.admin")) {
|
||||
p.sendMessage(mgr.msg("tree.destroy-fail-owner"));
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = destroyers.get(p.getUniqueId());
|
||||
if (last == null || now - last > 10_000) {
|
||||
destroyers.put(p.getUniqueId(), now);
|
||||
p.sendMessage(mgr.msg(t.level == Level.SAPLING ? "tree.destroy-sapling" : "tree.destroy-warning"));
|
||||
return;
|
||||
}
|
||||
destroyers.remove(p.getUniqueId());
|
||||
destroy(p, t);
|
||||
}
|
||||
|
||||
private static boolean Tag_isSapling(Material m) {
|
||||
return org.bukkit.Tag.SAPLINGS.isTagged(m);
|
||||
}
|
||||
|
||||
private void destroy(Player p, Tree t) {
|
||||
boolean refund = isEnded() && mgr.cfg().getBoolean("magic-tree.holiday-ends.resource-back", true);
|
||||
removeTree(t);
|
||||
if (refund) {
|
||||
for (var e : t.spent.entrySet()) RewardUtil.give(p, new ItemStack(e.getKey(), e.getValue()));
|
||||
p.sendMessage(mgr.msg("tree.destroy-refund"));
|
||||
} else {
|
||||
p.sendMessage(mgr.msg("tree.destroy-complete"));
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTree(Tree t) {
|
||||
World w = Bukkit.getWorld(t.world);
|
||||
for (String k : t.presents) {
|
||||
presentMap.remove(k);
|
||||
if (w == null) continue;
|
||||
String[] pp = k.split(";");
|
||||
w.getBlockAt(Integer.parseInt(pp[1]), Integer.parseInt(pp[2]), Integer.parseInt(pp[3])).setType(Material.AIR, false);
|
||||
}
|
||||
t.presents.clear();
|
||||
clearBlocks(t);
|
||||
trees.remove(t);
|
||||
save();
|
||||
}
|
||||
|
||||
public void info(Player p, Tree t) {
|
||||
p.sendMessage(mgr.msg("tree.info", "%owner%", t.ownerName, "%level%", t.level.name()));
|
||||
}
|
||||
|
||||
// ================= Geschenke =================
|
||||
|
||||
private long cooldown(Level lv) {
|
||||
return mgr.cfg().getLong("magic-tree.levels." + lv.key + ".gift-cooldown", -1);
|
||||
}
|
||||
|
||||
public int spawnPresentsEverywhere() {
|
||||
int n = 0;
|
||||
for (Tree t : new ArrayList<>(trees)) if (t.level != Level.SAPLING && spawnPresent(t, true)) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
private boolean spawnPresent(Tree t, boolean ignoreCap) {
|
||||
Location base = t.loc();
|
||||
if (base == null || !base.getWorld().isChunkLoaded(t.x >> 4, t.z >> 4)) return false;
|
||||
int cap = mgr.cfg().getInt("magic-tree.max-presents-per-tree", 5);
|
||||
if (!ignoreCap && t.presents.size() >= cap) return false;
|
||||
List<String> skins = mgr.cfg().getStringList("magic-tree.presents");
|
||||
if (skins.isEmpty()) return false;
|
||||
|
||||
World w = base.getWorld();
|
||||
for (int attempt = 0; attempt < 12; attempt++) {
|
||||
double ang = ChristmasManager.RANDOM.nextDouble() * Math.PI * 2;
|
||||
double dist = t.level.radius + 1 + ChristmasManager.RANDOM.nextInt(2);
|
||||
int bx = t.x + (int) Math.round(Math.cos(ang) * dist);
|
||||
int bz = t.z + (int) Math.round(Math.sin(ang) * dist);
|
||||
for (int by = t.y + 2; by >= t.y - 3; by--) {
|
||||
Block b = w.getBlockAt(bx, by, bz);
|
||||
if (b.getType().isAir() && w.getBlockAt(bx, by - 1, bz).getType().isSolid() && !isProtected(b)) {
|
||||
setSkin(b, skins.get(ChristmasManager.RANDOM.nextInt(skins.size())));
|
||||
t.presents.add(key(b));
|
||||
presentMap.put(key(b), t);
|
||||
w.spawnParticle(Particle.SNOWFLAKE, b.getLocation().add(0.5, 0.8, 0.5), 15, 0.3, 0.3, 0.3, 0.02);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setSkin(Block b, String url) {
|
||||
b.setType(Material.PLAYER_HEAD, false);
|
||||
if (!(b.getState() instanceof Skull sk)) return;
|
||||
try {
|
||||
PlayerProfile prof = Bukkit.createPlayerProfile(UUID.nameUUIDFromBytes(url.getBytes()), "present");
|
||||
PlayerTextures tex = prof.getTextures();
|
||||
tex.setSkin(new URL(url));
|
||||
prof.setTextures(tex);
|
||||
sk.setOwnerProfile(prof);
|
||||
sk.update(false, false);
|
||||
} catch (MalformedURLException ignored) { }
|
||||
}
|
||||
|
||||
public void openPresent(Player p, Tree t, Block b) {
|
||||
String k = key(b);
|
||||
presentMap.remove(k);
|
||||
t.presents.remove(k);
|
||||
b.setType(Material.AIR, false);
|
||||
b.getWorld().spawnParticle(Particle.END_ROD, b.getLocation().add(0.5, 0.5, 0.5), 20, 0.3, 0.3, 0.3, 0.05);
|
||||
b.getWorld().playSound(b.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.4f);
|
||||
save();
|
||||
|
||||
if (mgr.cfg().getBoolean("magic-tree.luck.enabled", false)
|
||||
&& ChristmasManager.RANDOM.nextInt(100) >= mgr.cfg().getInt("magic-tree.luck.chance", 75)) {
|
||||
p.sendMessage(mgr.msg("gift.unluck-message"));
|
||||
return;
|
||||
}
|
||||
ItemStack gift = randomGift();
|
||||
if (gift != null) {
|
||||
RewardUtil.give(p, gift);
|
||||
p.sendMessage(mgr.msg("gift.opened"));
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack randomGift() {
|
||||
List<String> mats = mgr.cfg().getStringList("magic-tree.gifts");
|
||||
int total = mats.size() + customGifts.size();
|
||||
if (total == 0) return null;
|
||||
int i = ChristmasManager.RANDOM.nextInt(total);
|
||||
if (i < mats.size()) return ChristmasManager.parseItem(mats.get(i));
|
||||
return customGifts.get(i - mats.size()).clone();
|
||||
}
|
||||
|
||||
// ================= Partikel-Show =================
|
||||
|
||||
private static final Particle DUST = lookup("DUST", "REDSTONE");
|
||||
private static final Color[] SPIRAL = {Color.RED, Color.fromRGB(255, 200, 50), Color.LIME};
|
||||
|
||||
private static Particle lookup(String... names) {
|
||||
for (String n : names) {
|
||||
try { return Particle.valueOf(n); } catch (IllegalArgumentException ignored) { }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void dust(World w, double x, double y, double z, Color c, float size) {
|
||||
if (DUST == null) return;
|
||||
w.spawnParticle(DUST, x, y, z, 1, 0, 0, 0, 0, new Particle.DustOptions(c, size));
|
||||
}
|
||||
|
||||
private boolean playerNear(Location l, double dist) {
|
||||
for (Player p : l.getWorld().getPlayers()) {
|
||||
if (p.getLocation().distanceSquared(l) <= dist * dist) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Leuchtende Spirale, Lichter, Stern an der Spitze und Glitzer ueber Geschenken. */
|
||||
private void fx() {
|
||||
if (!enabled() || isEnded() || !mgr.cfg().getBoolean("magic-tree.particles.enable", true)) return;
|
||||
fxTick++;
|
||||
double intensity = Math.max(0.2, Math.min(5.0, mgr.cfg().getDouble("magic-tree.particles.intensity", 2.0)));
|
||||
|
||||
for (Tree t : trees) {
|
||||
Location base = t.loc();
|
||||
if (base == null || !base.getWorld().isChunkLoaded(t.x >> 4, t.z >> 4) || !playerNear(base, 40)) continue;
|
||||
World w = base.getWorld();
|
||||
double cx = t.x + 0.5, cz = t.z + 0.5;
|
||||
int h = height(t.level);
|
||||
|
||||
if (t.level == Level.SAPLING) {
|
||||
if (fxTick % 4 == 0) {
|
||||
double a = ChristmasManager.RANDOM.nextDouble() * Math.PI * 2;
|
||||
w.spawnParticle(Particle.END_ROD, cx + Math.cos(a) * 0.6, t.y + 0.3 + ChristmasManager.RANDOM.nextDouble() * 0.6,
|
||||
cz + Math.sin(a) * 0.6, 1, 0, 0, 0, 0.002);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
double yStart = t.y + 2.2;
|
||||
double yEnd = t.y + h + 0.5;
|
||||
int strands = t.level.ordinal() + 1; // 2, 3, 4 Straenge je nach Stufe
|
||||
int trail = Math.max(3, (int) Math.round(5 * intensity)); // Punkte pro Strang (laengerer Schweif)
|
||||
|
||||
// Spirale in Rot, Gold und Gruen mit leuchtendem Schweif, die nach oben wandert
|
||||
for (int sIdx = 0; sIdx < strands; sIdx++) {
|
||||
double f0 = ((fxTick * 0.012) + sIdx / (double) strands) % 1.0;
|
||||
double phase0 = fxTick * 0.30 + sIdx * (Math.PI * 2 / strands);
|
||||
Color c = SPIRAL[(sIdx + fxTick / 8) % SPIRAL.length];
|
||||
for (int k = 0; k < trail; k++) {
|
||||
double f = Math.max(0, f0 - k * 0.012);
|
||||
double phase = phase0 - k * 0.22;
|
||||
double rad = Math.max(0.3, t.level.radius * (1 - f) + 0.35);
|
||||
dust(w, cx + Math.cos(phase) * rad, yStart + f * (yEnd - yStart), cz + Math.sin(phase) * rad,
|
||||
c, k == 0 ? 1.5f : 1.1f);
|
||||
}
|
||||
}
|
||||
|
||||
// Leuchtende Ringe, die ueber den Baum pulsieren
|
||||
if (fxTick % 6 == 0) {
|
||||
int rings = 3;
|
||||
int pointsPerRing = Math.max(8, (int) Math.round(10 * intensity));
|
||||
for (int r = 0; r < rings; r++) {
|
||||
double f = ((fxTick * 0.01) + r / (double) rings) % 1.0;
|
||||
double rad = Math.max(0.3, t.level.radius * (1 - f) + 0.45);
|
||||
double y = yStart + f * (yEnd - yStart);
|
||||
Color c = SPIRAL[(r + fxTick / 12) % SPIRAL.length];
|
||||
for (int i = 0; i < pointsPerRing; i++) {
|
||||
double a = i * Math.PI * 2 / pointsPerRing;
|
||||
dust(w, cx + Math.cos(a) * rad, y, cz + Math.sin(a) * rad, c, 0.9f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Funkelnde Lichter auf der Baumoberflaeche
|
||||
int twinkles = Math.max(1, (int) Math.round(3 * intensity));
|
||||
for (int i = 0; i < twinkles; i++) {
|
||||
double f = ChristmasManager.RANDOM.nextDouble();
|
||||
double a = ChristmasManager.RANDOM.nextDouble() * Math.PI * 2;
|
||||
double rad = Math.max(0.3, t.level.radius * (1 - f) + 0.2);
|
||||
double x = cx + Math.cos(a) * rad, y = yStart + f * (yEnd - yStart), z = cz + Math.sin(a) * rad;
|
||||
if (ChristmasManager.RANDOM.nextInt(3) == 0) {
|
||||
dust(w, x, y, z, Color.fromRGB(255, 240, 170), 1.3f);
|
||||
} else {
|
||||
w.spawnParticle(Particle.END_ROD, x, y, z, 1, 0, 0, 0, 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
// Schneeflocken, die von den Aesten nach unten fallen
|
||||
int flakes = Math.max(1, (int) Math.round(2 * intensity));
|
||||
for (int i = 0; i < flakes; i++) {
|
||||
double f = ChristmasManager.RANDOM.nextDouble() * 0.8;
|
||||
double a = ChristmasManager.RANDOM.nextDouble() * Math.PI * 2;
|
||||
double rad = Math.max(0.3, t.level.radius * (1 - f) + 0.3);
|
||||
w.spawnParticle(Particle.SNOWFLAKE, cx + Math.cos(a) * rad, yStart + f * (yEnd - yStart), cz + Math.sin(a) * rad,
|
||||
0, 0, -1, 0, 0.12);
|
||||
}
|
||||
|
||||
// Stern an der Spitze
|
||||
w.spawnParticle(Particle.END_ROD, cx, t.y + h + 1.3, cz, Math.max(2, (int) Math.round(3 * intensity)), 0.2, 0.2, 0.2, 0.004);
|
||||
if (fxTick % 12 == 0) {
|
||||
// Strahlenkranz um den Stern
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double a = i * Math.PI / 4 + fxTick * 0.05;
|
||||
w.spawnParticle(Particle.END_ROD, cx + Math.cos(a) * 0.5, t.y + h + 1.3, cz + Math.sin(a) * 0.5, 1, 0, 0, 0, 0.01);
|
||||
}
|
||||
}
|
||||
if (t.level == Level.MAGIC_TREE && fxTick % 20 == 0) {
|
||||
w.spawnParticle(Particle.END_ROD, cx, t.y + h + 1.3, cz, (int) (24 * intensity), 0.1, 0.1, 0.1, 0.15);
|
||||
w.spawnParticle(Particle.SNOWFLAKE, cx, t.y + h + 0.5, cz, (int) (20 * intensity), 1.6, 0.8, 1.6, 0.02);
|
||||
}
|
||||
|
||||
// Bereit zum Wachsen: leuchtender Ring am Boden
|
||||
if (t.req.isEmpty() && t.level.next() != null && fxTick % 10 == 0) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
double a = i * Math.PI * 2 / 16;
|
||||
w.spawnParticle(Particle.END_ROD, cx + Math.cos(a) * (t.level.radius + 0.8), t.y + 0.2,
|
||||
cz + Math.sin(a) * (t.level.radius + 0.8), 1, 0, 0, 0, 0.004);
|
||||
}
|
||||
}
|
||||
|
||||
// Glitzer ueber den Geschenken
|
||||
if (fxTick % 8 == 0) {
|
||||
for (String key : t.presents) {
|
||||
String[] pp = key.split(";");
|
||||
if (pp.length < 4) continue;
|
||||
try {
|
||||
dust(w, Integer.parseInt(pp[1]) + 0.5, Integer.parseInt(pp[2]) + 1.0, Integer.parseInt(pp[3]) + 0.5,
|
||||
Color.fromRGB(255, 215, 0), 0.9f);
|
||||
} catch (NumberFormatException ignored) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================= Tick =================
|
||||
|
||||
private void tick() {
|
||||
if (!enabled()) return;
|
||||
if (isEnded()) {
|
||||
if (!endAnnounced && !trees.isEmpty()) announceEnd();
|
||||
return;
|
||||
}
|
||||
for (Tree t : trees) {
|
||||
Location base = t.loc();
|
||||
if (base == null || !base.getWorld().isChunkLoaded(t.x >> 4, t.z >> 4)) continue;
|
||||
if (!t.indexed) index(t);
|
||||
|
||||
Location top = base.clone().add(0.5, height(t.level) + 0.5, 0.5);
|
||||
int r = t.level.radius;
|
||||
base.getWorld().spawnParticle(Particle.SNOWFLAKE, top, 6, r, 1, r, 0.01);
|
||||
if (t.req.isEmpty() && t.level.next() != null) {
|
||||
base.getWorld().spawnParticle(Particle.END_ROD, top, 3, 0.3, 0.3, 0.3, 0.02);
|
||||
}
|
||||
|
||||
long cd = cooldown(t.level);
|
||||
if (cd > 0) {
|
||||
t.counter--;
|
||||
if (t.counter <= 0) {
|
||||
spawnPresent(t, false);
|
||||
t.counter = cd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Mannequin;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
import org.bukkit.profile.PlayerTextures;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Spielermodell mit Skin ueber das Mannequin (Minecraft 1.21.9+), ohne Zusatz-Plugin.
|
||||
* Diese Klasse darf nur geladen werden, wenn {@link #supported()} true ergibt.
|
||||
*/
|
||||
final class MannequinSupport {
|
||||
private MannequinSupport() {}
|
||||
|
||||
static boolean supported() {
|
||||
try {
|
||||
EntityType.valueOf("MANNEQUIN");
|
||||
Class.forName("org.bukkit.entity.Mannequin");
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static LivingEntity spawn(Location loc, String name, String skinUrl, String skinPlayer) {
|
||||
return loc.getWorld().spawn(loc, Mannequin.class, m -> {
|
||||
m.setPersistent(false);
|
||||
m.setInvulnerable(true);
|
||||
m.setGravity(false);
|
||||
m.setSilent(true);
|
||||
m.setCustomName(name);
|
||||
m.setCustomNameVisible(true);
|
||||
// Diese Methoden heissen je nach Version anders oder fehlen: einzeln und fehlertolerant aufrufen
|
||||
optional(m, "setImmovable", true);
|
||||
optional(m, "setHideDescription", true);
|
||||
applySkin(m, skinUrl, skinPlayer);
|
||||
});
|
||||
}
|
||||
|
||||
private static void optional(Object target, String method, boolean value) {
|
||||
try {
|
||||
target.getClass().getMethod(method, boolean.class).invoke(target, value);
|
||||
} catch (Throwable ignored) { }
|
||||
}
|
||||
|
||||
private static void applySkin(Mannequin m, String skinUrl, String skinPlayer) {
|
||||
if (skinUrl != null && !skinUrl.isBlank()) {
|
||||
try {
|
||||
PlayerProfile prof = Bukkit.createPlayerProfile(UUID.nameUUIDFromBytes(skinUrl.getBytes()), "santa");
|
||||
PlayerTextures tex = prof.getTextures();
|
||||
tex.setSkin(new URL(skinUrl));
|
||||
prof.setTextures(tex);
|
||||
m.setPlayerProfile(prof);
|
||||
} catch (MalformedURLException ignored) { }
|
||||
} else if (skinPlayer != null && !skinPlayer.isBlank()) {
|
||||
PlayerProfile prof = Bukkit.createPlayerProfile(skinPlayer);
|
||||
prof.update().thenAccept(updated -> Bukkit.getScheduler().runTask(
|
||||
ChristmasManager.get().plugin(), () -> { if (m.isValid()) m.setPlayerProfile(updated); }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Villager;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.MerchantRecipe;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Weihnachtsmarkt-Staende: Haendler (Villager), bei denen Spieler Plaetzchen und Deko gegen Items tauschen. */
|
||||
public final class MarketManager {
|
||||
private final ChristmasManager mgr;
|
||||
private final NamespacedKey stallKey;
|
||||
|
||||
private final File spawnFile;
|
||||
private Location spawn;
|
||||
|
||||
MarketManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.spawnFile = new File(mgr.plugin().getDataFolder(), "market.yml");
|
||||
this.stallKey = new NamespacedKey(mgr.plugin(), "market_stall");
|
||||
}
|
||||
|
||||
// ---------- Marktpunkt (Ziel der Portale) ----------
|
||||
|
||||
void load() {
|
||||
spawn = null;
|
||||
if (!spawnFile.exists()) return;
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(spawnFile);
|
||||
World w = Bukkit.getWorld(y.getString("spawn.world", ""));
|
||||
if (w == null) return;
|
||||
spawn = new Location(w, y.getDouble("spawn.x"), y.getDouble("spawn.y"), y.getDouble("spawn.z"),
|
||||
(float) y.getDouble("spawn.yaw"), (float) y.getDouble("spawn.pitch"));
|
||||
}
|
||||
|
||||
/** Ziel der Portale oder null, wenn noch nicht gesetzt. */
|
||||
public Location spawn() {
|
||||
if (spawn == null || spawn.getWorld() == null) load();
|
||||
return spawn == null ? null : spawn.clone();
|
||||
}
|
||||
|
||||
/** Hebt den Marktpunkt auf: Portale fuehren dann nirgendwohin, bis ein Admin ihn neu setzt. */
|
||||
public void clearSpawn() {
|
||||
spawn = null;
|
||||
if (spawnFile.exists()) spawnFile.delete();
|
||||
}
|
||||
|
||||
public void setSpawn(Location l) {
|
||||
spawn = l.clone();
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
y.set("spawn.world", l.getWorld().getName());
|
||||
y.set("spawn.x", l.getX());
|
||||
y.set("spawn.y", l.getY());
|
||||
y.set("spawn.z", l.getZ());
|
||||
y.set("spawn.yaw", l.getYaw());
|
||||
y.set("spawn.pitch", l.getPitch());
|
||||
try {
|
||||
y.save(spawnFile);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("market.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> stallIds() {
|
||||
ConfigurationSection s = mgr.cfg().getConfigurationSection("market.stalls");
|
||||
return s == null ? new LinkedHashSet<>() : s.getKeys(false);
|
||||
}
|
||||
|
||||
public boolean isStall(Entity e) {
|
||||
return e.getPersistentDataContainer().has(stallKey, PersistentDataType.STRING);
|
||||
}
|
||||
|
||||
public boolean spawn(Location loc, String id) {
|
||||
ConfigurationSection s = mgr.cfg().getConfigurationSection("market.stalls." + id);
|
||||
if (s == null) return false;
|
||||
World w = loc.getWorld();
|
||||
Villager v = w.spawn(loc, Villager.class, vill -> {
|
||||
vill.setAI(false);
|
||||
vill.setInvulnerable(true);
|
||||
vill.setPersistent(true);
|
||||
vill.setRemoveWhenFarAway(false);
|
||||
vill.setSilent(true);
|
||||
vill.setCustomName(ChristmasManager.color(s.getString("name", "&6Markt")));
|
||||
vill.setCustomNameVisible(true);
|
||||
vill.setVillagerType(Villager.Type.SNOW);
|
||||
vill.setProfession(profession(s.getString("profession", "LIBRARIAN")));
|
||||
vill.setVillagerLevel(5);
|
||||
vill.getPersistentDataContainer().set(stallKey, PersistentDataType.STRING, id);
|
||||
});
|
||||
applyTrades(v, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Villager.Profession profession(String name) {
|
||||
Villager.Profession p = Registry.VILLAGER_PROFESSION.get(NamespacedKey.minecraft(name.toLowerCase()));
|
||||
return p != null ? p : Villager.Profession.LIBRARIAN;
|
||||
}
|
||||
|
||||
private void applyTrades(Villager v, String id) {
|
||||
List<MerchantRecipe> recipes = new ArrayList<>();
|
||||
for (Map<?, ?> t : mgr.cfg().getMapList("market.stalls." + id + ".trades")) {
|
||||
ItemStack give = ChristmasManager.parseItem(String.valueOf(t.get("give")));
|
||||
if (give == null) {
|
||||
mgr.plugin().getLogger().warning("Markt '" + id + "': ungueltiges Item '" + t.get("give") + "'");
|
||||
continue;
|
||||
}
|
||||
MerchantRecipe r = new MerchantRecipe(give, 0, 100000, false, 0, 0f);
|
||||
Object price = t.get("price");
|
||||
int added = 0;
|
||||
if (price instanceof List<?> list) {
|
||||
for (Object o : list) {
|
||||
ItemStack p = ChristmasManager.parseItem(String.valueOf(o));
|
||||
if (p != null && added < 2) { r.addIngredient(p); added++; }
|
||||
}
|
||||
} else if (price != null) {
|
||||
ItemStack p = ChristmasManager.parseItem(String.valueOf(price));
|
||||
if (p != null) { r.addIngredient(p); added++; }
|
||||
}
|
||||
if (added == 0) continue;
|
||||
// Rabatte durch Ruf/Nachfrage abschalten (nur auf Paper vorhanden, sonst ignorieren)
|
||||
try {
|
||||
r.getClass().getMethod("setIgnoreDiscounts", boolean.class).invoke(r, true);
|
||||
} catch (Throwable ignored) { }
|
||||
recipes.add(r);
|
||||
}
|
||||
v.setRecipes(recipes);
|
||||
}
|
||||
|
||||
/** Wendet nach /xmas reload die Angebote auf alle geladenen Staende an. */
|
||||
public void refreshAll() {
|
||||
for (World w : Bukkit.getWorlds()) {
|
||||
for (Villager v : w.getEntitiesByClass(Villager.class)) {
|
||||
String id = v.getPersistentDataContainer().get(stallKey, PersistentDataType.STRING);
|
||||
if (id != null && mgr.cfg().getConfigurationSection("market.stalls." + id) != null) applyTrades(v, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeNearest(Player p, double radius) {
|
||||
Villager best = null;
|
||||
double bestDist = Double.MAX_VALUE;
|
||||
for (Entity e : p.getNearbyEntities(radius, radius, radius)) {
|
||||
if (e instanceof Villager v && isStall(v)) {
|
||||
double d = v.getLocation().distanceSquared(p.getLocation());
|
||||
if (d < bestDist) { bestDist = d; best = v; }
|
||||
}
|
||||
}
|
||||
if (best == null) return false;
|
||||
best.remove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import de.mviper.adventskalender.RewardUtil;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.type.Candle;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Portale zum Weihnachtsmarkt: Spieler bauen sie per Befehl, der Markt wird vom Admin gesetzt. */
|
||||
public final class PortalManager {
|
||||
|
||||
private record Portal(UUID owner, String ownerName, String world, int x, int y, int z, boolean axisX) {}
|
||||
|
||||
private record Part(int u, int v, Material material) {}
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private final File file;
|
||||
private final List<Portal> portals = new ArrayList<>();
|
||||
private final Set<String> blocks = new HashSet<>();
|
||||
private final Map<UUID, Long> entering = new HashMap<>();
|
||||
private final Map<UUID, Long> cooldown = new HashMap<>();
|
||||
private final Map<UUID, Location> origin = new HashMap<>();
|
||||
private BukkitTask task;
|
||||
private int tickCount;
|
||||
|
||||
PortalManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.file = new File(mgr.plugin().getDataFolder(), "portals.yml");
|
||||
}
|
||||
|
||||
// ---------- Form ----------
|
||||
|
||||
/** Rahmen aus Schneebloecken: zwei Pfosten (u=-2/+2), Sturz, Seelaterne in der Mitte, Schneeschicht und Kerzen oben. */
|
||||
private static List<Part> parts() {
|
||||
List<Part> list = new ArrayList<>();
|
||||
for (int v = 0; v <= 3; v++) {
|
||||
list.add(new Part(-2, v, Material.SNOW_BLOCK));
|
||||
list.add(new Part(2, v, Material.SNOW_BLOCK));
|
||||
}
|
||||
for (int u = -1; u <= 1; u++) {
|
||||
list.add(new Part(u, 3, u == 0 ? Material.SEA_LANTERN : Material.SNOW_BLOCK));
|
||||
list.add(new Part(u, 4, Material.SNOW));
|
||||
}
|
||||
list.add(new Part(-2, 4, Material.RED_CANDLE));
|
||||
list.add(new Part(2, 4, Material.RED_CANDLE));
|
||||
return list;
|
||||
}
|
||||
|
||||
private static int bx(Portal p, int u) { return p.axisX() ? p.x() + u : p.x(); }
|
||||
private static int bz(Portal p, int u) { return p.axisX() ? p.z() : p.z() + u; }
|
||||
|
||||
private static String key(String world, int x, int y, int z) { return world + ";" + x + ";" + y + ";" + z; }
|
||||
|
||||
// ---------- Persistenz ----------
|
||||
|
||||
void load() {
|
||||
portals.clear();
|
||||
blocks.clear();
|
||||
if (file.exists()) {
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(file);
|
||||
ConfigurationSection s = y.getConfigurationSection("portals");
|
||||
if (s != null) {
|
||||
for (String k : s.getKeys(false)) {
|
||||
ConfigurationSection c = s.getConfigurationSection(k);
|
||||
if (c == null) continue;
|
||||
try {
|
||||
Portal p = new Portal(UUID.fromString(c.getString("owner", "")), c.getString("owner-name", "?"),
|
||||
c.getString("world", "world"), c.getInt("x"), c.getInt("y"), c.getInt("z"),
|
||||
c.getBoolean("axis-x", true));
|
||||
portals.add(p);
|
||||
index(p);
|
||||
} catch (IllegalArgumentException ignored) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
stop();
|
||||
task = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::tick, 100L, 5L);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
}
|
||||
|
||||
private void save() {
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
int i = 0;
|
||||
for (Portal p : portals) {
|
||||
String k = "portals." + (i++);
|
||||
y.set(k + ".owner", p.owner().toString());
|
||||
y.set(k + ".owner-name", p.ownerName());
|
||||
y.set(k + ".world", p.world());
|
||||
y.set(k + ".x", p.x());
|
||||
y.set(k + ".y", p.y());
|
||||
y.set(k + ".z", p.z());
|
||||
y.set(k + ".axis-x", p.axisX());
|
||||
}
|
||||
try {
|
||||
y.save(file);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("portals.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void index(Portal p) {
|
||||
for (Part part : parts()) blocks.add(key(p.world(), bx(p, part.u()), p.y() + part.v(), bz(p, part.u())));
|
||||
}
|
||||
|
||||
public boolean isProtected(Block b) {
|
||||
return blocks.contains(key(b.getWorld().getName(), b.getX(), b.getY(), b.getZ()));
|
||||
}
|
||||
|
||||
public int countOf(UUID owner) {
|
||||
int n = 0;
|
||||
for (Portal p : portals) if (p.owner().equals(owner)) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
// ---------- Bauen und Entfernen ----------
|
||||
|
||||
private List<String> cost() { return mgr.cfg().getStringList("portal.cost"); }
|
||||
|
||||
/** @return Nachrichten-Schluessel */
|
||||
public String create(Player p, Block ground) {
|
||||
if (!mgr.cfg().getBoolean("portal.enable", true)) return "portal-disabled";
|
||||
if (mgr.market().spawn() == null) return "portal-no-market";
|
||||
List<String> worlds = mgr.cfg().getStringList("portal.allowed-worlds");
|
||||
if (!worlds.isEmpty() && !worlds.contains(p.getWorld().getName())) return "portal-world";
|
||||
int limit = Math.max(1, mgr.cfg().getInt("portal.limit-per-player", 1));
|
||||
if (!p.hasPermission("xmas.admin") && countOf(p.getUniqueId()) >= limit) return "portal-limit";
|
||||
|
||||
BlockFace f = p.getFacing();
|
||||
boolean axisX = f == BlockFace.NORTH || f == BlockFace.SOUTH;
|
||||
Block base = ground.getRelative(BlockFace.UP);
|
||||
Portal portal = new Portal(p.getUniqueId(), p.getName(), p.getWorld().getName(),
|
||||
base.getX(), base.getY(), base.getZ(), axisX);
|
||||
World w = p.getWorld();
|
||||
|
||||
// Platz pruefen: Rahmen und Oeffnung muessen frei sein
|
||||
for (Part part : parts()) {
|
||||
if (!free(w.getBlockAt(bx(portal, part.u()), portal.y() + part.v(), bz(portal, part.u())))) return "portal-no-space";
|
||||
}
|
||||
for (int u = -1; u <= 1; u++) {
|
||||
for (int v = 0; v <= 2; v++) {
|
||||
if (!free(w.getBlockAt(bx(portal, u), portal.y() + v, bz(portal, u)))) return "portal-no-space";
|
||||
}
|
||||
}
|
||||
// Boden unter dem Rahmen muss fest sein
|
||||
for (int u = -2; u <= 2; u++) {
|
||||
if (!w.getBlockAt(bx(portal, u), portal.y() - 1, bz(portal, u)).getType().isSolid()) return "portal-no-space";
|
||||
}
|
||||
|
||||
List<String> cost = cost();
|
||||
if (!p.hasPermission("xmas.admin")) {
|
||||
if (!ChristmasManager.hasAll(p, cost)) return "portal-need";
|
||||
ChristmasManager.takeAll(p, cost);
|
||||
}
|
||||
|
||||
for (Part part : parts()) {
|
||||
Block b = w.getBlockAt(bx(portal, part.u()), portal.y() + part.v(), bz(portal, part.u()));
|
||||
if (part.material() == Material.RED_CANDLE) {
|
||||
Candle c = (Candle) Material.RED_CANDLE.createBlockData();
|
||||
c.setLit(true);
|
||||
b.setBlockData(c, false);
|
||||
} else {
|
||||
b.setType(part.material(), false);
|
||||
}
|
||||
}
|
||||
portals.add(portal);
|
||||
index(portal);
|
||||
save();
|
||||
w.spawnParticle(Particle.END_ROD, bx(portal, 0) + 0.5, portal.y() + 1.5, bz(portal, 0) + 0.5, 60, 1, 1, 1, 0.08);
|
||||
w.playSound(p.getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 1f, 1.3f);
|
||||
return "portal-created";
|
||||
}
|
||||
|
||||
private static boolean free(Block b) {
|
||||
return b.getType().isAir() || (b.isPassable() && !b.isLiquid());
|
||||
}
|
||||
|
||||
/** Entfernt das Portal, zu dem der Block gehoert. */
|
||||
public String remove(Player p, Block target) {
|
||||
Portal found = null;
|
||||
for (Portal portal : portals) {
|
||||
if (!portal.world().equals(target.getWorld().getName())) continue;
|
||||
for (Part part : parts()) {
|
||||
if (bx(portal, part.u()) == target.getX() && portal.y() + part.v() == target.getY()
|
||||
&& bz(portal, part.u()) == target.getZ()) { found = portal; break; }
|
||||
}
|
||||
if (found != null) break;
|
||||
}
|
||||
if (found == null) return "portal-none";
|
||||
if (!found.owner().equals(p.getUniqueId()) && !p.hasPermission("xmas.admin")) return "portal-not-owner";
|
||||
|
||||
World w = Bukkit.getWorld(found.world());
|
||||
if (w != null) {
|
||||
for (Part part : parts()) {
|
||||
blocks.remove(key(found.world(), bx(found, part.u()), found.y() + part.v(), bz(found, part.u())));
|
||||
w.getBlockAt(bx(found, part.u()), found.y() + part.v(), bz(found, part.u())).setType(Material.AIR, false);
|
||||
}
|
||||
}
|
||||
portals.remove(found);
|
||||
save();
|
||||
if (mgr.cfg().getBoolean("portal.refund", true) && found.owner().equals(p.getUniqueId())) {
|
||||
for (String c : cost()) {
|
||||
ItemStack it = ChristmasManager.parseItem(c);
|
||||
if (it != null) RewardUtil.give(p, it);
|
||||
}
|
||||
}
|
||||
return "portal-removed";
|
||||
}
|
||||
|
||||
// ---------- Ruecksprung ----------
|
||||
|
||||
public String back(Player p) {
|
||||
Location l = origin.get(p.getUniqueId());
|
||||
if (l == null) return "portal-back-none";
|
||||
p.teleport(l);
|
||||
return "portal-back-ok";
|
||||
}
|
||||
|
||||
// ---------- Tick: Partikel und Teleport ----------
|
||||
|
||||
private void tick() {
|
||||
tickCount++;
|
||||
long now = System.currentTimeMillis();
|
||||
long delay = Math.max(1, mgr.cfg().getInt("portal.teleport-delay-seconds", 3)) * 1000L;
|
||||
long cd = Math.max(0, mgr.cfg().getInt("portal.cooldown-seconds", 10)) * 1000L;
|
||||
Set<UUID> inside = new HashSet<>();
|
||||
|
||||
for (Portal portal : portals) {
|
||||
World w = Bukkit.getWorld(portal.world());
|
||||
if (w == null || !w.isChunkLoaded(portal.x() >> 4, portal.z() >> 4)) continue;
|
||||
boolean near = false;
|
||||
for (Player p : w.getPlayers()) {
|
||||
if (p.getLocation().distanceSquared(new Location(w, portal.x(), portal.y(), portal.z())) <= 40 * 40) near = true;
|
||||
if (p.getLocation().distanceSquared(new Location(w, portal.x(), portal.y(), portal.z())) > 6 * 6) continue;
|
||||
if (!inOpening(portal, p.getLocation())) continue;
|
||||
inside.add(p.getUniqueId());
|
||||
|
||||
if (cooldown.getOrDefault(p.getUniqueId(), 0L) > now) continue;
|
||||
long start = entering.computeIfAbsent(p.getUniqueId(), k -> now);
|
||||
long left = delay - (now - start);
|
||||
if (left > 0) {
|
||||
p.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(
|
||||
mgr.raw("portal-countdown").replace("%s%", String.valueOf((left + 999) / 1000))));
|
||||
w.spawnParticle(Particle.END_ROD, p.getLocation().add(0, 1, 0), 3, 0.3, 0.6, 0.3, 0.02);
|
||||
} else {
|
||||
teleport(p, portal);
|
||||
entering.remove(p.getUniqueId());
|
||||
cooldown.put(p.getUniqueId(), now + cd);
|
||||
}
|
||||
}
|
||||
if (!near) continue;
|
||||
|
||||
// Schimmernde Oeffnung
|
||||
if (tickCount % 2 == 0) {
|
||||
double cx = bx(portal, 0) + 0.5, cz = bz(portal, 0) + 0.5;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
double u = (ChristmasManager.RANDOM.nextDouble() * 2 - 1) * 1.4;
|
||||
double v = ChristmasManager.RANDOM.nextDouble() * 3;
|
||||
double x = portal.axisX() ? cx + u : cx;
|
||||
double z = portal.axisX() ? cz : cz + u;
|
||||
Color c = ChristmasManager.RANDOM.nextBoolean() ? Color.fromRGB(120, 200, 255) : Color.WHITE;
|
||||
spawnDust(w, x, portal.y() + v, z, c);
|
||||
}
|
||||
w.spawnParticle(Particle.SNOWFLAKE, cx, portal.y() + 1.5, cz, 6, 0.8, 1.2, 0.8, 0.01);
|
||||
w.spawnParticle(Particle.END_ROD, cx, portal.y() + 1.5, cz, 2, 0.9, 1.3, 0.9, 0.01);
|
||||
}
|
||||
}
|
||||
entering.keySet().removeIf(id -> !inside.contains(id));
|
||||
}
|
||||
|
||||
private void spawnDust(World w, double x, double y, double z, Color c) {
|
||||
Particle dust;
|
||||
try { dust = Particle.valueOf("DUST"); } catch (IllegalArgumentException e) {
|
||||
try { dust = Particle.valueOf("REDSTONE"); } catch (IllegalArgumentException ex) { return; }
|
||||
}
|
||||
w.spawnParticle(dust, x, y, z, 1, 0, 0, 0, 0, new Particle.DustOptions(c, 1.2f));
|
||||
}
|
||||
|
||||
private boolean inOpening(Portal p, Location l) {
|
||||
double x = l.getX(), y = l.getY(), z = l.getZ();
|
||||
if (y < p.y() - 0.2 || y > p.y() + 3) return false;
|
||||
if (p.axisX()) {
|
||||
return x >= p.x() - 1 && x < p.x() + 2 && z >= p.z() - 0.4 && z < p.z() + 1.4;
|
||||
}
|
||||
return z >= p.z() - 1 && z < p.z() + 2 && x >= p.x() - 0.4 && x < p.x() + 1.4;
|
||||
}
|
||||
|
||||
private void teleport(Player p, Portal portal) {
|
||||
Location dest = mgr.market().spawn();
|
||||
if (dest == null) { p.sendMessage(mgr.msg("portal-no-market")); return; }
|
||||
|
||||
// Rueckweg: etwas vor dem Portal, auf der Seite, von der der Spieler kam
|
||||
double side = portal.axisX() ? Math.signum(p.getLocation().getZ() - (portal.z() + 0.5))
|
||||
: Math.signum(p.getLocation().getX() - (portal.x() + 0.5));
|
||||
if (side == 0) side = 1;
|
||||
double ox = portal.axisX() ? portal.x() + 0.5 : portal.x() + 0.5 + side * 2.5;
|
||||
double oz = portal.axisX() ? portal.z() + 0.5 + side * 2.5 : portal.z() + 0.5;
|
||||
Location back = new Location(Bukkit.getWorld(portal.world()), ox, portal.y(), oz,
|
||||
portal.axisX() ? (side > 0 ? 180f : 0f) : (side > 0 ? 90f : -90f), 0f);
|
||||
origin.put(p.getUniqueId(), back);
|
||||
|
||||
p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation().add(0, 1, 0), 30, 0.4, 0.8, 0.4, 0.05);
|
||||
p.teleport(dest);
|
||||
p.getWorld().playSound(dest, Sound.ENTITY_ENDERMAN_TELEPORT, 1f, 1.4f);
|
||||
p.getWorld().spawnParticle(Particle.SNOWFLAKE, dest.clone().add(0, 1, 0), 40, 0.6, 0.8, 0.6, 0.03);
|
||||
|
||||
net.md_5.bungee.api.chat.TextComponent msg = new net.md_5.bungee.api.chat.TextComponent(
|
||||
mgr.prefixed(mgr.raw("portal-teleported")) + " ");
|
||||
net.md_5.bungee.api.chat.TextComponent click = new net.md_5.bungee.api.chat.TextComponent(mgr.raw("portal-back-click"));
|
||||
click.setClickEvent(new net.md_5.bungee.api.chat.ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.RUN_COMMAND, "/xmas market back"));
|
||||
msg.addExtra(click);
|
||||
p.spigot().sendMessage(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.text.Normalizer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** Weihnachtsquiz: Frage im Chat, die schnellste richtige Antwort gewinnt. */
|
||||
public final class QuizManager {
|
||||
private final ChristmasManager mgr;
|
||||
private volatile boolean active;
|
||||
private volatile List<String> answers = List.of();
|
||||
private volatile List<Map<?, ?>> rewards = List.of();
|
||||
private String shownAnswer = "";
|
||||
private final AtomicBoolean answered = new AtomicBoolean(false);
|
||||
private BukkitTask timeoutTask;
|
||||
private BukkitTask autoTask;
|
||||
private int lastIndex = -1;
|
||||
|
||||
QuizManager(ChristmasManager mgr) { this.mgr = mgr; }
|
||||
|
||||
public boolean isActive() { return active; }
|
||||
|
||||
void start() {
|
||||
stop();
|
||||
if (!mgr.cfg().getBoolean("quiz.auto.enable", false)) return;
|
||||
long ticks = 20L * 60 * Math.max(1, mgr.cfg().getInt("quiz.auto.interval-minutes", 20));
|
||||
autoTask = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), () -> {
|
||||
int min = mgr.cfg().getInt("quiz.auto.min-players", 2);
|
||||
if (!active && Bukkit.getOnlinePlayers().size() >= min) ask(-1);
|
||||
}, ticks, ticks);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (autoTask != null) autoTask.cancel();
|
||||
autoTask = null;
|
||||
cancelRound();
|
||||
}
|
||||
|
||||
private void cancelRound() {
|
||||
active = false;
|
||||
if (timeoutTask != null) timeoutTask.cancel();
|
||||
timeoutTask = null;
|
||||
}
|
||||
|
||||
public int questionCount() { return mgr.cfg().getMapList("quiz.questions").size(); }
|
||||
|
||||
/** Startet eine Frage (index -1 = zufaellig). false, wenn schon aktiv oder keine Fragen vorhanden. */
|
||||
public boolean ask(int index) {
|
||||
if (active) return false;
|
||||
List<Map<?, ?>> questions = mgr.cfg().getMapList("quiz.questions");
|
||||
if (questions.isEmpty()) return false;
|
||||
int i = index;
|
||||
if (i < 0 || i >= questions.size()) {
|
||||
do {
|
||||
i = ChristmasManager.RANDOM.nextInt(questions.size());
|
||||
} while (questions.size() > 1 && i == lastIndex);
|
||||
}
|
||||
lastIndex = i;
|
||||
Map<?, ?> q = questions.get(i);
|
||||
|
||||
List<String> ans = new ArrayList<>();
|
||||
Object a = q.get("a");
|
||||
if (a instanceof List<?> list) for (Object o : list) ans.add(normalize(String.valueOf(o)));
|
||||
else if (a != null) ans.add(normalize(String.valueOf(a)));
|
||||
if (ans.isEmpty()) return false;
|
||||
|
||||
List<Map<?, ?>> rw = new ArrayList<>();
|
||||
Object r = q.get("reward");
|
||||
if (r instanceof List<?> rl) for (Object o : rl) if (o instanceof Map<?, ?> m) rw.add(m);
|
||||
if (rw.isEmpty()) rw = mgr.cfg().getMapList("quiz.default-reward");
|
||||
|
||||
String shown = a instanceof List<?> l && !l.isEmpty() ? String.valueOf(l.get(0)) : String.valueOf(a);
|
||||
answers = ans;
|
||||
rewards = rw;
|
||||
shownAnswer = shown;
|
||||
answered.set(false);
|
||||
active = true;
|
||||
|
||||
int seconds = Math.max(5, mgr.cfg().getInt("quiz.answer-seconds", 30));
|
||||
Bukkit.broadcastMessage(mgr.msg("quiz-question", "%question%", String.valueOf(q.get("q")),
|
||||
"%time%", String.valueOf(seconds)));
|
||||
timeoutTask = Bukkit.getScheduler().runTaskLater(mgr.plugin(), () -> {
|
||||
if (!active) return;
|
||||
cancelRound();
|
||||
Bukkit.broadcastMessage(mgr.msg("quiz-timeout", "%answer%", shownAnswer));
|
||||
}, seconds * 20L);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean skip() {
|
||||
if (!active) return false;
|
||||
cancelRound();
|
||||
Bukkit.broadcastMessage(mgr.msg("quiz-timeout", "%answer%", shownAnswer));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String normalize(String s) {
|
||||
// Umlaute wie ae/oe/ue behandeln, damit "Türchen" und "Tuerchen" beide richtig sind
|
||||
String lower = s.trim().toLowerCase().replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss");
|
||||
String n = Normalizer.normalize(lower, Normalizer.Form.NFD).replaceAll("\\p{M}", "");
|
||||
return n.replaceAll("[^a-z0-9 ]", "").replaceAll("\\s+", " ").trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aus dem (asynchronen) Chat-Ereignis aufgerufen. true = richtige Antwort, Nachricht sollte unterdrueckt werden.
|
||||
*/
|
||||
public boolean onChat(Player p, String message) {
|
||||
if (!active) return false;
|
||||
if (!answers.contains(normalize(message))) return false;
|
||||
if (!answered.compareAndSet(false, true)) return true; // jemand war schneller
|
||||
Bukkit.getScheduler().runTask(mgr.plugin(), () -> win(p));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void win(Player p) {
|
||||
cancelRound();
|
||||
Bukkit.broadcastMessage(mgr.msg("quiz-win", "%player%", p.getName(), "%answer%", shownAnswer));
|
||||
for (Map<?, ?> reward : rewards) ChristmasExtras.applyMap(p, reward);
|
||||
p.sendMessage(mgr.msg("quiz-reward"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.boss.BarColor;
|
||||
import org.bukkit.boss.BarStyle;
|
||||
import org.bukkit.boss.BossBar;
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
import org.bukkit.entity.Horse;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.LeatherArmorMeta;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
import org.bukkit.profile.PlayerTextures;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Santa-Claus-Event: Ein Weihnachtsmann steht (oder laeuft) an einem Ort, hinterlaesst Schnee und wirft Geschenke ab. */
|
||||
public final class SantaEvent {
|
||||
private final ChristmasManager mgr;
|
||||
private boolean active;
|
||||
private Location origin;
|
||||
private Location loc;
|
||||
private LivingEntity stand;
|
||||
private BossBar bar;
|
||||
private BukkitTask task;
|
||||
private int total;
|
||||
private int left;
|
||||
private final Map<Block, Long> trail = new HashMap<>();
|
||||
private final SantaMelody melody;
|
||||
private long lastDropMsg;
|
||||
|
||||
// Schlitten-Anflug
|
||||
private boolean flying;
|
||||
private BukkitTask flightTask;
|
||||
private final List<LivingEntity> flightEntities = new java.util.ArrayList<>();
|
||||
private ArmorStand sleighStand;
|
||||
private int flightStep;
|
||||
|
||||
SantaEvent(ChristmasManager mgr) { this.mgr = mgr; this.melody = new SantaMelody(mgr); }
|
||||
|
||||
public boolean isActive() { return active; }
|
||||
public Location getLocation() { return loc == null ? null : loc.clone(); }
|
||||
|
||||
/** Waehlt zufaellig einen konfigurierten Ort, null wenn keiner vorhanden. */
|
||||
public Location randomLocation() {
|
||||
List<Map<?, ?>> list = mgr.cfg().getMapList("santa.locations");
|
||||
for (int tries = 0; tries < 10 && !list.isEmpty(); tries++) {
|
||||
Map<?, ?> m = list.get(ChristmasManager.RANDOM.nextInt(list.size()));
|
||||
World w = Bukkit.getWorld(String.valueOf(m.get("world")));
|
||||
if (w == null) continue;
|
||||
return new Location(w, num(m.get("x")), num(m.get("y")), num(m.get("z")));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static double num(Object o) { return o instanceof Number n ? n.doubleValue() : 0; }
|
||||
|
||||
public static String formatDuration(int sec) {
|
||||
if (sec % 3600 == 0 && sec >= 3600) return (sec / 3600) + "h";
|
||||
if (sec % 60 == 0 && sec >= 60) return (sec / 60) + " min";
|
||||
return sec + "s";
|
||||
}
|
||||
|
||||
public void start(int seconds, Location where) {
|
||||
if (active || where == null || where.getWorld() == null) return;
|
||||
active = true;
|
||||
origin = where.clone();
|
||||
loc = where.clone();
|
||||
total = left = Math.max(5, seconds);
|
||||
|
||||
stand = createModel(loc);
|
||||
|
||||
bar = Bukkit.createBossBar(barTitle(), BarColor.RED, BarStyle.SOLID);
|
||||
if (mgr.cfg().getBoolean("santa.sleigh.enable", true)) beginFlight();
|
||||
Bukkit.broadcastMessage(mgr.msg("santa-started",
|
||||
"%x%", String.valueOf(loc.getBlockX()), "%y%", String.valueOf(loc.getBlockY()),
|
||||
"%z%", String.valueOf(loc.getBlockZ()), "%world%", loc.getWorld().getName(),
|
||||
"%duration%", formatDuration(total)));
|
||||
|
||||
lastDropMsg = 0;
|
||||
melody.play(loc.getWorld());
|
||||
startWalking();
|
||||
|
||||
int dropEvery = Math.max(1, mgr.cfg().getInt("santa.drop-interval", 3));
|
||||
double chance = mgr.cfg().getDouble("santa.drop-chance", 100);
|
||||
task = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), () -> {
|
||||
left--;
|
||||
fadeTrail(false);
|
||||
if (left <= 0) { stop(true); return; }
|
||||
bar.setTitle(barTitle());
|
||||
bar.setProgress(Math.max(0, Math.min(1, left / (double) total)));
|
||||
for (Player p : Bukkit.getOnlinePlayers()) bar.addPlayer(p);
|
||||
loc.getWorld().spawnParticle(Particle.SNOWFLAKE, loc.clone().add(0, 2, 0), 20, 1, 1, 1, 0.02);
|
||||
if ((total - left) % dropEvery == 0 && ChristmasManager.RANDOM.nextDouble() * 100 < chance) dropGift();
|
||||
}, 20L, 20L);
|
||||
}
|
||||
|
||||
/** Erzeugt Santas Modell (Spielermodell oder Ruestungsstaender) an der Stelle. */
|
||||
LivingEntity createModel(Location loc) {
|
||||
LivingEntity stand = null;
|
||||
String name = ChristmasManager.color(mgr.cfg().getString("santa.name", "&cSanta Claus"));
|
||||
String skin = mgr.cfg().getString("santa.skin-url", "");
|
||||
String model = mgr.cfg().getString("santa.model", "AUTO").toUpperCase();
|
||||
String skinPlayer = mgr.cfg().getString("santa.skin-player", "");
|
||||
boolean mannequin = !model.equals("ARMOR_STAND") && MannequinSupport.supported();
|
||||
if (mannequin) {
|
||||
try {
|
||||
stand = MannequinSupport.spawn(loc, name, skin, skinPlayer);
|
||||
if (skin.isBlank() && skinPlayer.isBlank()) {
|
||||
// Ohne eigenen Skin: rote Lederruestung, damit er wie Santa aussieht
|
||||
var eq = stand.getEquipment();
|
||||
eq.setHelmet(dyed(Material.LEATHER_HELMET));
|
||||
eq.setChestplate(dyed(Material.LEATHER_CHESTPLATE));
|
||||
eq.setLeggings(dyed(Material.LEATHER_LEGGINGS));
|
||||
eq.setBoots(dyed(Material.LEATHER_BOOTS));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
mgr.plugin().getLogger().warning("Mannequin konnte nicht erzeugt werden, nutze Rüstungsständer: " + t);
|
||||
if (stand != null) stand.remove();
|
||||
stand = null;
|
||||
}
|
||||
}
|
||||
if (stand == null) {
|
||||
stand = loc.getWorld().spawn(loc, ArmorStand.class, s -> {
|
||||
s.setPersistent(false);
|
||||
s.setInvulnerable(true);
|
||||
s.setGravity(false);
|
||||
s.setArms(true);
|
||||
s.setCustomName(name);
|
||||
s.setCustomNameVisible(true);
|
||||
ItemStack head = skin.isBlank() ? dyed(Material.LEATHER_HELMET) : skull(skin);
|
||||
s.getEquipment().setHelmet(head);
|
||||
s.getEquipment().setChestplate(dyed(Material.LEATHER_CHESTPLATE));
|
||||
s.getEquipment().setLeggings(dyed(Material.LEATHER_LEGGINGS));
|
||||
s.getEquipment().setBoots(dyed(Material.LEATHER_BOOTS));
|
||||
});
|
||||
}
|
||||
return stand;
|
||||
}
|
||||
|
||||
private String barTitle() {
|
||||
return mgr.raw("santa-bossbar").replace("%time%", String.valueOf(left));
|
||||
}
|
||||
|
||||
private static ItemStack dyed(Material m) {
|
||||
ItemStack it = new ItemStack(m);
|
||||
LeatherArmorMeta meta = (LeatherArmorMeta) it.getItemMeta();
|
||||
if (meta != null) { meta.setColor(Color.RED); it.setItemMeta(meta); }
|
||||
return it;
|
||||
}
|
||||
|
||||
private static ItemStack skull(String url) {
|
||||
ItemStack it = new ItemStack(Material.PLAYER_HEAD);
|
||||
SkullMeta meta = (SkullMeta) it.getItemMeta();
|
||||
if (meta == null) return it;
|
||||
try {
|
||||
PlayerProfile prof = Bukkit.createPlayerProfile(UUID.nameUUIDFromBytes(url.getBytes()), "santa");
|
||||
PlayerTextures tex = prof.getTextures();
|
||||
tex.setSkin(new URL(url));
|
||||
prof.setTextures(tex);
|
||||
meta.setOwnerProfile(prof);
|
||||
it.setItemMeta(meta);
|
||||
} catch (MalformedURLException ignored) { }
|
||||
return it;
|
||||
}
|
||||
|
||||
// ---------- Laufen und Schneespur ----------
|
||||
|
||||
// Fluessiges Laufen: jeden Tick ein kleiner Schritt zu einem Zufallsziel, sanftes Drehen, kurze Pausen.
|
||||
private BukkitTask walkTask;
|
||||
private Location walkTarget;
|
||||
private int walkPause;
|
||||
private float walkYaw;
|
||||
private Block lastStep;
|
||||
|
||||
private void startWalking() {
|
||||
stopWalking();
|
||||
if (mgr.cfg().getDouble("santa.walk-radius", 0) <= 0) return;
|
||||
walkYaw = loc.getYaw();
|
||||
walkTask = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::walkStep, 20L, 1L);
|
||||
}
|
||||
|
||||
private void stopWalking() {
|
||||
if (walkTask != null) walkTask.cancel();
|
||||
walkTask = null;
|
||||
walkTarget = null;
|
||||
}
|
||||
|
||||
/** Sucht die Standhoehe an x/z (Fuss-Block frei, darunter fest, 2 Bloecke Platz); NaN = nicht begehbar. */
|
||||
private double groundAt(World w, double x, double z, double fromY) {
|
||||
int bx = (int) Math.floor(x), bz = (int) Math.floor(z);
|
||||
for (int by = (int) Math.floor(fromY) + 1; by >= (int) Math.floor(fromY) - 3; by--) {
|
||||
Block feet = w.getBlockAt(bx, by, bz);
|
||||
if (!feet.getType().isSolid() && !feet.isLiquid()
|
||||
&& w.getBlockAt(bx, by + 1, bz).getType().isAir()
|
||||
&& w.getBlockAt(bx, by - 1, bz).getType().isSolid()) {
|
||||
return by;
|
||||
}
|
||||
}
|
||||
return Double.NaN;
|
||||
}
|
||||
|
||||
private void walkStep() {
|
||||
if (!active || flying || stand == null || stand.isDead()) return;
|
||||
double radius = mgr.cfg().getDouble("santa.walk-radius", 0);
|
||||
if (radius <= 0) return;
|
||||
if (walkPause > 0) { walkPause--; return; }
|
||||
World w = loc.getWorld();
|
||||
|
||||
if (walkTarget == null) {
|
||||
double ang = ChristmasManager.RANDOM.nextDouble() * Math.PI * 2;
|
||||
double dist = 2 + ChristmasManager.RANDOM.nextDouble() * Math.max(1, radius - 1);
|
||||
double tx = origin.getX() + Math.cos(ang) * Math.min(dist, radius);
|
||||
double tz = origin.getZ() + Math.sin(ang) * Math.min(dist, radius);
|
||||
double gy = groundAt(w, tx, tz, loc.getY());
|
||||
if (Double.isNaN(gy)) { walkPause = 20; return; }
|
||||
walkTarget = new Location(w, tx, gy, tz);
|
||||
}
|
||||
|
||||
double dx = walkTarget.getX() - loc.getX(), dz = walkTarget.getZ() - loc.getZ();
|
||||
double dist = Math.sqrt(dx * dx + dz * dz);
|
||||
if (dist < 0.25) {
|
||||
walkTarget = null;
|
||||
walkPause = 30 + ChristmasManager.RANDOM.nextInt(80); // stehen bleiben und umsehen
|
||||
return;
|
||||
}
|
||||
|
||||
// sanft zum Ziel drehen
|
||||
float want = (float) Math.toDegrees(Math.atan2(-dx, dz));
|
||||
float diff = ((want - walkYaw + 540f) % 360f) - 180f;
|
||||
walkYaw += Math.max(-10f, Math.min(10f, diff));
|
||||
if (Math.abs(diff) > 45f) return; // erst drehen, dann loslaufen
|
||||
|
||||
double speed = Math.max(0.02, mgr.cfg().getDouble("santa.walk-speed", 0.07));
|
||||
double rad = Math.toRadians(walkYaw);
|
||||
double nx = loc.getX() - Math.sin(rad) * speed;
|
||||
double nz = loc.getZ() + Math.cos(rad) * speed;
|
||||
double ox = nx - origin.getX(), oz = nz - origin.getZ();
|
||||
double gy = (ox * ox + oz * oz > radius * radius) ? Double.NaN : groundAt(w, nx, nz, loc.getY());
|
||||
if (Double.isNaN(gy) || Math.abs(gy - loc.getY()) > 1.01) {
|
||||
walkTarget = null; // Hindernis oder Rand: neues Ziel nach kurzer Pause
|
||||
walkPause = 10 + ChristmasManager.RANDOM.nextInt(20);
|
||||
return;
|
||||
}
|
||||
double ny = loc.getY() + Math.max(-0.25, Math.min(0.25, gy - loc.getY()));
|
||||
Location next = new Location(w, nx, ny, nz, walkYaw, 0f);
|
||||
stand.teleport(next);
|
||||
loc = next;
|
||||
|
||||
// Schritte: Geraeusch und kurze Schneespur
|
||||
if (stand.getTicksLived() % 9 == 0) w.playSound(next, Sound.BLOCK_SNOW_STEP, 0.6f, 1f);
|
||||
Block feet = w.getBlockAt(next.getBlockX(), (int) Math.floor(gy), next.getBlockZ());
|
||||
if (!feet.equals(lastStep)) {
|
||||
lastStep = feet;
|
||||
if (mgr.cfg().getBoolean("santa.snow-trail.enabled", true) && feet.getType() == Material.AIR) {
|
||||
feet.setType(Material.SNOW, false);
|
||||
long fade = Math.max(1, mgr.cfg().getLong("santa.snow-trail.fade-seconds", 5)) * 1000L;
|
||||
trail.put(feet, System.currentTimeMillis() + fade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fadeTrail(boolean all) {
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator<Map.Entry<Block, Long>> it = trail.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
var e = it.next();
|
||||
if (all || e.getValue() <= now) {
|
||||
if (e.getKey().getType() == Material.SNOW) e.getKey().setType(Material.AIR, false);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Schlitten-Anflug ----------
|
||||
|
||||
private void beginFlight() {
|
||||
int seconds = Math.max(5, Math.min(total - 2, mgr.cfg().getInt("santa.sleigh.flight-seconds", 15)));
|
||||
double dist = Math.max(20, mgr.cfg().getDouble("santa.sleigh.distance", 70));
|
||||
double height = Math.max(8, mgr.cfg().getDouble("santa.sleigh.height", 30));
|
||||
double ang = ChristmasManager.RANDOM.nextDouble() * Math.PI * 2;
|
||||
Vector dir = new Vector(Math.cos(ang), 0, Math.sin(ang));
|
||||
Location from = origin.clone().add(dir.clone().multiply(-dist));
|
||||
from.setY(origin.getY() + height);
|
||||
final int steps = seconds * 10;
|
||||
|
||||
flying = true;
|
||||
flightStep = 0;
|
||||
World w = origin.getWorld();
|
||||
|
||||
sleighStand = w.spawn(from, ArmorStand.class, s -> {
|
||||
s.setPersistent(false);
|
||||
s.setInvulnerable(true);
|
||||
s.setGravity(false);
|
||||
s.setVisible(false);
|
||||
s.getEquipment().setHelmet(new ItemStack(Material.RED_SHULKER_BOX));
|
||||
});
|
||||
List<String> names = mgr.cfg().getStringList("santa.sleigh.reindeer-names");
|
||||
for (int i = 0; i < 2; i++) {
|
||||
final String n = names.size() > i ? names.get(i) : "Rudolph";
|
||||
Horse horse = w.spawn(from, Horse.class, h -> {
|
||||
h.setPersistent(false);
|
||||
h.setInvulnerable(true);
|
||||
h.setGravity(false);
|
||||
h.setAI(false);
|
||||
h.setSilent(true);
|
||||
h.setColor(Horse.Color.BROWN);
|
||||
h.setCustomName(ChristmasManager.color("&6" + n));
|
||||
h.setCustomNameVisible(true);
|
||||
});
|
||||
flightEntities.add(horse);
|
||||
}
|
||||
|
||||
flightTask = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), () -> {
|
||||
if (stand == null || !stand.isValid()) { endFlight(); return; }
|
||||
double t = Math.min(1.0, flightStep / (double) steps);
|
||||
// Sinkflug: erst waagerecht, dann weich landen
|
||||
double eased = t * t * (3 - 2 * t);
|
||||
Location pos = from.clone().add(origin.clone().subtract(from).toVector().multiply(t));
|
||||
pos.setY(from.getY() + (origin.getY() - from.getY()) * eased);
|
||||
pos.setDirection(dir);
|
||||
loc = pos.clone();
|
||||
|
||||
stand.teleport(pos);
|
||||
if (sleighStand != null) {
|
||||
sleighStand.teleport(pos.clone().subtract(dir.clone().multiply(3)).subtract(0, 1.5, 0));
|
||||
}
|
||||
for (int i = 0; i < flightEntities.size(); i++) {
|
||||
flightEntities.get(i).teleport(pos.clone().add(dir.clone().multiply(3 + i * 2.5)));
|
||||
}
|
||||
w.spawnParticle(Particle.END_ROD, pos.clone().subtract(dir.clone().multiply(4)), 6, 0.3, 0.3, 0.3, 0.02);
|
||||
w.spawnParticle(Particle.SNOWFLAKE, pos.clone().subtract(dir.clone().multiply(2)), 10, 0.6, 0.4, 0.6, 0.02);
|
||||
if (flightStep % 10 == 0) w.playSound(pos, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 2f, 1.5f);
|
||||
|
||||
flightStep++;
|
||||
if (flightStep > steps) endFlight();
|
||||
}, 0L, 2L);
|
||||
}
|
||||
|
||||
private void endFlight() {
|
||||
if (flightTask != null) flightTask.cancel();
|
||||
flightTask = null;
|
||||
flying = false;
|
||||
if (sleighStand != null) { sleighStand.remove(); sleighStand = null; }
|
||||
for (LivingEntity e : flightEntities) e.remove();
|
||||
flightEntities.clear();
|
||||
if (active && stand != null && stand.isValid() && origin != null) {
|
||||
loc = origin.clone();
|
||||
stand.teleport(origin);
|
||||
origin.getWorld().spawnParticle(Particle.CLOUD, origin.clone().add(0, 1, 0), 40, 1, 0.5, 1, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Geschenke ----------
|
||||
|
||||
private void dropGift() {
|
||||
List<String> gifts = mgr.cfg().getStringList("santa.gifts");
|
||||
if (gifts.isEmpty()) return;
|
||||
String pick = gifts.get(ChristmasManager.RANDOM.nextInt(gifts.size()));
|
||||
ItemStack it;
|
||||
if (pick.toLowerCase().startsWith("cracker:")) {
|
||||
String[] p = pick.split(":");
|
||||
int amount = 1;
|
||||
if (p.length > 2) {
|
||||
try { amount = Integer.parseInt(p[2].trim()); } catch (NumberFormatException ignored) { }
|
||||
}
|
||||
it = mgr.crackers().create(p[1].trim(), amount);
|
||||
} else {
|
||||
it = ChristmasManager.parseItem(pick);
|
||||
}
|
||||
if (it == null) return;
|
||||
double r = mgr.cfg().getDouble("santa.radius", 6);
|
||||
Location at = loc.clone().add((ChristmasManager.RANDOM.nextDouble() - 0.5) * 2 * r, 3,
|
||||
(ChristmasManager.RANDOM.nextDouble() - 0.5) * 2 * r);
|
||||
loc.getWorld().dropItem(at, it);
|
||||
loc.getWorld().spawnParticle(Particle.END_ROD, at, 10, 0.2, 0.2, 0.2, 0.02);
|
||||
loc.getWorld().playSound(at, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.6f, 1.2f);
|
||||
// Nachricht nur selten, sonst wird der Chat bei jedem Geschenk zugespammt
|
||||
long every = mgr.cfg().getLong("santa.drop-message-interval", 60) * 1000L;
|
||||
long now = System.currentTimeMillis();
|
||||
if (every > 0 && now - lastDropMsg >= every) {
|
||||
lastDropMsg = now;
|
||||
for (Player p : loc.getWorld().getPlayers()) {
|
||||
if (p.getLocation().distanceSquared(loc) <= 64 * 64) p.sendMessage(mgr.msg("santa-dropped"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(boolean announce) {
|
||||
if (!active) return;
|
||||
active = false;
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
fadeTrail(true);
|
||||
melody.stop();
|
||||
stopWalking();
|
||||
endFlightSilently();
|
||||
if (stand != null) stand.remove();
|
||||
stand = null;
|
||||
if (bar != null) { bar.removeAll(); bar = null; }
|
||||
if (announce) Bukkit.broadcastMessage(mgr.msg("santa-stopped"));
|
||||
}
|
||||
|
||||
private void endFlightSilently() {
|
||||
if (flightTask != null) flightTask.cancel();
|
||||
flightTask = null;
|
||||
flying = false;
|
||||
if (sleighStand != null) { sleighStand.remove(); sleighStand = null; }
|
||||
for (LivingEntity e : flightEntities) e.remove();
|
||||
flightEntities.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.SoundCategory;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
/** Spielt beim Santa-Event eine Weihnachtsmelodie mit Glocken (Notenblock-Klaenge, keine Dateien noetig). */
|
||||
final class SantaMelody {
|
||||
// Melodie: {Halbtoene ueber C4 (-1 = Pause), Dauer in Viertelschlag-Achteln (1 = Achtel)}
|
||||
private static final int[][] JINGLE = {
|
||||
{4, 2}, {4, 2}, {4, 4}, {4, 2}, {4, 2}, {4, 4}, {4, 2}, {7, 2}, {0, 3}, {2, 1}, {4, 8},
|
||||
{5, 2}, {5, 2}, {5, 3}, {5, 1}, {5, 2}, {4, 2}, {4, 2}, {4, 1}, {4, 1}, {4, 2}, {2, 2}, {2, 2}, {4, 2}, {2, 4}, {7, 4},
|
||||
{4, 2}, {4, 2}, {4, 4}, {4, 2}, {4, 2}, {4, 4}, {4, 2}, {7, 2}, {0, 3}, {2, 1}, {4, 8},
|
||||
{5, 2}, {5, 2}, {5, 3}, {5, 1}, {5, 2}, {4, 2}, {4, 2}, {4, 1}, {4, 1}, {7, 2}, {7, 2}, {5, 2}, {2, 2}, {0, 8},
|
||||
};
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private BukkitTask task;
|
||||
|
||||
SantaMelody(ChristmasManager mgr) { this.mgr = mgr; }
|
||||
|
||||
void play(World world) {
|
||||
stop();
|
||||
if (!mgr.cfg().getBoolean("santa.melody.enable", true)) return;
|
||||
final float volume = (float) mgr.cfg().getDouble("santa.melody.volume", 1.0);
|
||||
final boolean everyone = mgr.cfg().getBoolean("santa.melody.everyone", false);
|
||||
final int tickPerUnit = Math.max(1, mgr.cfg().getInt("santa.melody.tempo", 3));
|
||||
final int[] pos = {0};
|
||||
final int[] wait = {0};
|
||||
task = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), () -> {
|
||||
if (wait[0]-- > 0) return;
|
||||
if (pos[0] >= JINGLE.length) { stop(); return; }
|
||||
int[] n = JINGLE[pos[0]++];
|
||||
float pitch = (float) Math.pow(2.0, (n[0] + 6 - 12) / 12.0); // C4 = Notenblock-Ton 6
|
||||
for (Player p : everyone ? Bukkit.getOnlinePlayers() : world.getPlayers()) {
|
||||
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BELL, SoundCategory.RECORDS, volume, pitch);
|
||||
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, SoundCategory.RECORDS, volume * 0.5f, pitch);
|
||||
}
|
||||
wait[0] = n[1] * tickPerUnit - 1;
|
||||
}, 10L, 1L);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Schneeball-Minispiel: In einer Arena zaehlen Treffer mit Schneebaellen, die Besten bekommen Belohnungen. */
|
||||
public final class SnowballGame {
|
||||
private final ChristmasManager mgr;
|
||||
private final File file;
|
||||
private String arenaWorld;
|
||||
private double ax, ay, az;
|
||||
private int radius;
|
||||
private boolean active;
|
||||
private int left;
|
||||
private BukkitTask task;
|
||||
private BukkitTask arenaTask;
|
||||
private final Map<UUID, Integer> points = new HashMap<>();
|
||||
private final Map<UUID, String> names = new HashMap<>();
|
||||
|
||||
SnowballGame(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.file = new File(mgr.plugin().getDataFolder(), "snowball.yml");
|
||||
}
|
||||
|
||||
public boolean isActive() { return active; }
|
||||
public boolean hasArena() { return arenaWorld != null && radius > 0; }
|
||||
|
||||
void load() {
|
||||
startArenaTask();
|
||||
if (!file.exists()) return;
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(file);
|
||||
arenaWorld = y.getString("arena.world");
|
||||
ax = y.getDouble("arena.x");
|
||||
ay = y.getDouble("arena.y");
|
||||
az = y.getDouble("arena.z");
|
||||
radius = y.getInt("arena.radius", 0);
|
||||
}
|
||||
|
||||
public void setArena(Location center, int r) {
|
||||
arenaWorld = center.getWorld().getName();
|
||||
ax = center.getX();
|
||||
ay = center.getY();
|
||||
az = center.getZ();
|
||||
radius = Math.max(3, r);
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
y.set("arena.world", arenaWorld);
|
||||
y.set("arena.x", ax);
|
||||
y.set("arena.y", ay);
|
||||
y.set("arena.z", az);
|
||||
y.set("arena.radius", radius);
|
||||
try {
|
||||
y.save(file);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("snowball.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Laeuft immer, fuellt aber nur waehrend einer laufenden Runde die Schneebaelle der Spieler in der Arena auf. */
|
||||
private void startArenaTask() {
|
||||
if (arenaTask != null) arenaTask.cancel();
|
||||
arenaTask = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), () -> {
|
||||
if (!hasArena()) return;
|
||||
if (!active) return; // nur waehrend einer laufenden Runde
|
||||
int refill = Math.max(1, mgr.cfg().getInt("snowball.refill-amount", 16));
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (inArena(p)) refill(p, refill);
|
||||
}
|
||||
}, 20L, 20L);
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
stop(false);
|
||||
if (arenaTask != null) arenaTask.cancel();
|
||||
arenaTask = null;
|
||||
}
|
||||
|
||||
public boolean inArena(Player p) {
|
||||
if (!hasArena()) return false;
|
||||
World w = p.getWorld();
|
||||
if (!w.getName().equals(arenaWorld)) return false;
|
||||
double dx = p.getLocation().getX() - ax, dz = p.getLocation().getZ() - az;
|
||||
return dx * dx + dz * dz <= (double) radius * radius && Math.abs(p.getLocation().getY() - ay) <= 40;
|
||||
}
|
||||
|
||||
// ---------- Ablauf ----------
|
||||
|
||||
public boolean start(int seconds) {
|
||||
if (active || !hasArena()) return false;
|
||||
points.clear();
|
||||
names.clear();
|
||||
active = true;
|
||||
left = Math.max(10, seconds);
|
||||
Bukkit.broadcastMessage(mgr.msg("sb-started", "%time%", SantaEvent.formatDuration(left),
|
||||
"%world%", arenaWorld, "%x%", String.valueOf((int) ax), "%z%", String.valueOf((int) az)));
|
||||
int refill = Math.max(1, mgr.cfg().getInt("snowball.refill-amount", 16));
|
||||
for (Player p : Bukkit.getOnlinePlayers()) if (inArena(p)) refill(p, refill);
|
||||
task = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), () -> {
|
||||
left--;
|
||||
if (left <= 0) { stop(true); return; }
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (!inArena(p)) continue;
|
||||
String text = mgr.raw("sb-actionbar").replace("%points%", String.valueOf(points(p.getUniqueId())))
|
||||
.replace("%rank%", String.valueOf(Math.max(1, rank(p.getUniqueId()))))
|
||||
.replace("%time%", String.valueOf(left));
|
||||
p.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(text));
|
||||
}
|
||||
}, 20L, 20L);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void refill(Player p, int amount) {
|
||||
int have = 0;
|
||||
for (ItemStack it : p.getInventory().getContents()) {
|
||||
if (it != null && it.getType() == Material.SNOWBALL) have += it.getAmount();
|
||||
}
|
||||
if (have < amount) p.getInventory().addItem(new ItemStack(Material.SNOWBALL, amount - have));
|
||||
}
|
||||
|
||||
public void stop(boolean announce) {
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
if (!active) return;
|
||||
active = false;
|
||||
if (!announce) return;
|
||||
|
||||
List<Map.Entry<UUID, Integer>> top = ranking();
|
||||
Bukkit.broadcastMessage(mgr.msg("sb-ended"));
|
||||
for (int i = 0; i < Math.min(3, top.size()); i++) {
|
||||
var e = top.get(i);
|
||||
Bukkit.broadcastMessage(mgr.prefixed(mgr.raw("sb-top-line").replace("%rank%", String.valueOf(i + 1))
|
||||
.replace("%player%", names.getOrDefault(e.getKey(), "?"))
|
||||
.replace("%points%", String.valueOf(e.getValue()))));
|
||||
Player winner = Bukkit.getPlayer(e.getKey());
|
||||
if (winner != null) {
|
||||
for (Map<?, ?> reward : mgr.cfg().getMapList("snowball.rewards." + (i + 1))) {
|
||||
ChristmasExtras.applyMap(winner, reward);
|
||||
}
|
||||
winner.sendMessage(mgr.msg("sb-reward", "%rank%", String.valueOf(i + 1)));
|
||||
}
|
||||
}
|
||||
// Spieler bekommen ihre Schneebaelle nicht zurueck: einfach liegen lassen
|
||||
}
|
||||
|
||||
// ---------- Punkte ----------
|
||||
|
||||
public void onHit(Player shooter, Player victim) {
|
||||
if (!active || shooter.equals(victim) || !inArena(shooter) || !inArena(victim)) return;
|
||||
points.merge(shooter.getUniqueId(), Math.max(1, mgr.cfg().getInt("snowball.points-per-hit", 1)), Integer::sum);
|
||||
names.put(shooter.getUniqueId(), shooter.getName());
|
||||
}
|
||||
|
||||
public int points(UUID id) { return points.getOrDefault(id, 0); }
|
||||
|
||||
private List<Map.Entry<UUID, Integer>> ranking() {
|
||||
List<Map.Entry<UUID, Integer>> list = new ArrayList<>(points.entrySet());
|
||||
list.sort((a, b) -> Integer.compare(b.getValue(), a.getValue()));
|
||||
return list;
|
||||
}
|
||||
|
||||
public int rank(UUID id) {
|
||||
List<Map.Entry<UUID, Integer>> r = ranking();
|
||||
for (int i = 0; i < r.size(); i++) if (r.get(i).getKey().equals(id)) return i + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public List<String> topLines(int n) {
|
||||
List<String> out = new ArrayList<>();
|
||||
List<Map.Entry<UUID, Integer>> r = ranking();
|
||||
for (int i = 0; i < Math.min(n, r.size()); i++) {
|
||||
out.add(mgr.prefixed(mgr.raw("sb-top-line").replace("%rank%", String.valueOf(i + 1))
|
||||
.replace("%player%", names.getOrDefault(r.get(i).getKey(), "?"))
|
||||
.replace("%points%", String.valueOf(r.get(i).getValue()))));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import de.mviper.adventskalender.RewardUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Snowman;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.event.block.EntityBlockFormEvent;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Schneemaenner: Deko-Schneemaenner (per Befehl aufgestellt) und Spieler-Schneemaenner
|
||||
* (normal gebaut), die gelegentlich ein Geschenk abwerfen.
|
||||
*/
|
||||
public final class SnowmanManager {
|
||||
private static final String DECO = "deco";
|
||||
private static final String PLAYER = "player";
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private final NamespacedKey typeKey;
|
||||
private final NamespacedKey ownerKey;
|
||||
private final NamespacedKey homeKey;
|
||||
private final NamespacedKey walkKey;
|
||||
private final Map<UUID, Long> nextGift = new HashMap<>();
|
||||
private BukkitTask task;
|
||||
private final Set<Block> trail = new HashSet<>();
|
||||
|
||||
SnowmanManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.typeKey = new NamespacedKey(mgr.plugin(), "snowman_type");
|
||||
this.ownerKey = new NamespacedKey(mgr.plugin(), "snowman_owner");
|
||||
this.walkKey = new NamespacedKey(mgr.plugin(), "snowman_walk");
|
||||
this.homeKey = new NamespacedKey(mgr.plugin(), "snowman_home");
|
||||
}
|
||||
|
||||
void start() {
|
||||
stop();
|
||||
task = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::tick, 100L, 40L);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
for (Block b : trail) if (b.getType() == Material.SNOW) b.setType(Material.AIR, false);
|
||||
trail.clear();
|
||||
}
|
||||
|
||||
/** Schnee-Spur der Schneemaenner: standardmaessig verschwindet sie nach einigen Sekunden wieder. */
|
||||
public void onSnowForm(EntityBlockFormEvent e) {
|
||||
if (!(e.getEntity() instanceof Snowman)) return;
|
||||
if (e.getNewState().getType() != Material.SNOW) return;
|
||||
String mode = mgr.cfg().getString("snowman.trail", "fade").toLowerCase();
|
||||
if (mode.equals("keep")) return;
|
||||
if (mode.equals("none")) { e.setCancelled(true); return; }
|
||||
Block b = e.getBlock();
|
||||
trail.add(b);
|
||||
long ticks = Math.max(1, mgr.cfg().getLong("snowman.trail-fade-seconds", 8)) * 20L;
|
||||
Bukkit.getScheduler().runTaskLater(mgr.plugin(), () -> {
|
||||
if (trail.remove(b) && b.getType() == Material.SNOW) b.setType(Material.AIR, false);
|
||||
}, ticks);
|
||||
}
|
||||
|
||||
// ---------- Abfragen ----------
|
||||
|
||||
public boolean isDeco(Entity e) { return DECO.equals(type(e)); }
|
||||
|
||||
private String type(Entity e) {
|
||||
return e.getPersistentDataContainer().get(typeKey, PersistentDataType.STRING);
|
||||
}
|
||||
|
||||
private UUID owner(Entity e) {
|
||||
String s = e.getPersistentDataContainer().get(ownerKey, PersistentDataType.STRING);
|
||||
try { return s == null ? null : UUID.fromString(s); } catch (IllegalArgumentException ex) { return null; }
|
||||
}
|
||||
|
||||
// ---------- Deko ----------
|
||||
|
||||
public void spawnDeco(Location loc, String name) { spawnDeco(loc, name, null); }
|
||||
|
||||
/** walk: null = Standard aus der Config, true = laufend, false = stehend. */
|
||||
public void spawnDeco(Location loc, String name, Boolean walkMode) {
|
||||
boolean hat = mgr.cfg().getBoolean("snowman.deco.hat", true);
|
||||
boolean walk = walkMode != null ? walkMode : mgr.cfg().getBoolean("snowman.deco.walk", false);
|
||||
loc.getWorld().spawn(loc, Snowman.class, s -> {
|
||||
s.setAI(walk);
|
||||
setHome(s, loc);
|
||||
s.getPersistentDataContainer().set(walkKey, PersistentDataType.BYTE, (byte) (walk ? 1 : 0));
|
||||
s.setInvulnerable(true);
|
||||
s.setPersistent(true);
|
||||
s.setRemoveWhenFarAway(false);
|
||||
s.setSilent(true);
|
||||
s.setDerp(!hat);
|
||||
if (name != null && !name.isBlank()) {
|
||||
s.setCustomName(ChristmasManager.color(name));
|
||||
s.setCustomNameVisible(true);
|
||||
}
|
||||
s.getPersistentDataContainer().set(typeKey, PersistentDataType.STRING, DECO);
|
||||
});
|
||||
loc.getWorld().spawnParticle(Particle.SNOWFLAKE, loc.clone().add(0, 1, 0), 30, 0.4, 0.6, 0.4, 0.02);
|
||||
}
|
||||
|
||||
private void setHome(Entity e, Location l) {
|
||||
e.getPersistentDataContainer().set(homeKey, PersistentDataType.STRING,
|
||||
l.getX() + ";" + l.getY() + ";" + l.getZ());
|
||||
}
|
||||
|
||||
private Location home(Entity e) {
|
||||
String h = e.getPersistentDataContainer().get(homeKey, PersistentDataType.STRING);
|
||||
if (h == null) return null;
|
||||
try {
|
||||
String[] p = h.split(";");
|
||||
return new Location(e.getWorld(), Double.parseDouble(p[0]), Double.parseDouble(p[1]), Double.parseDouble(p[2]));
|
||||
} catch (Exception ex) { return null; }
|
||||
}
|
||||
|
||||
/** Laufende Deko-Schneemaenner: frei herumlaufen, aber im Umkreis um den Startpunkt bleiben. */
|
||||
private void walkDeco(Snowman s, boolean defaultWalk, double radius) {
|
||||
Byte flag = s.getPersistentDataContainer().get(walkKey, PersistentDataType.BYTE);
|
||||
boolean walk = flag != null ? flag == 1 : defaultWalk;
|
||||
if (s.hasAI() != walk) s.setAI(walk);
|
||||
Location home = home(s);
|
||||
if (home == null) { setHome(s, s.getLocation()); return; }
|
||||
if (!walk) {
|
||||
if (s.getLocation().distanceSquared(home) > 1) s.teleport(home);
|
||||
return;
|
||||
}
|
||||
double d = s.getLocation().distance(home);
|
||||
if (d <= radius) return;
|
||||
if (d > radius * 2.5 || !pathTo(s, home)) s.teleport(home);
|
||||
}
|
||||
|
||||
/** Laeuft per Paper-Pathfinder zurueck (per Reflection, damit Spigot weiter geht). */
|
||||
private boolean pathTo(Snowman s, Location target) {
|
||||
try {
|
||||
Object pf = s.getClass().getMethod("getPathfinder").invoke(s);
|
||||
Object r = pf.getClass().getMethod("moveTo", Location.class, double.class)
|
||||
.invoke(pf, target, 1.0D);
|
||||
return r instanceof Boolean b && b;
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Entfernt den naechsten Deko-Schneemann im Umkreis. */
|
||||
public boolean removeNearestDeco(Player p, double radius) {
|
||||
Snowman best = null;
|
||||
double bestDist = Double.MAX_VALUE;
|
||||
for (Entity e : p.getNearbyEntities(radius, radius, radius)) {
|
||||
if (e instanceof Snowman s && isDeco(s)) {
|
||||
double d = s.getLocation().distanceSquared(p.getLocation());
|
||||
if (d < bestDist) { bestDist = d; best = s; }
|
||||
}
|
||||
}
|
||||
if (best == null) return false;
|
||||
best.remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------- Spieler-Schneemaenner ----------
|
||||
|
||||
/** Wird aufgerufen, wenn ein Schneemann normal gebaut wurde. */
|
||||
public void onBuilt(Snowman s) {
|
||||
if (!mgr.cfg().getBoolean("snowman.player.enable", true)) return;
|
||||
Player builder = null;
|
||||
double best = 12 * 12;
|
||||
for (Player p : s.getWorld().getPlayers()) {
|
||||
double d = p.getLocation().distanceSquared(s.getLocation());
|
||||
if (d < best) { best = d; builder = p; }
|
||||
}
|
||||
if (builder == null) return;
|
||||
|
||||
PersistentDataContainer pdc = s.getPersistentDataContainer();
|
||||
pdc.set(typeKey, PersistentDataType.STRING, PLAYER);
|
||||
pdc.set(ownerKey, PersistentDataType.STRING, builder.getUniqueId().toString());
|
||||
String name = mgr.cfg().getString("snowman.player.name", "&f%player%s Schneemann");
|
||||
s.setCustomName(ChristmasManager.color(name.replace("%player%", builder.getName())));
|
||||
s.setCustomNameVisible(true);
|
||||
s.setPersistent(true);
|
||||
s.setRemoveWhenFarAway(false);
|
||||
nextGift.put(s.getUniqueId(), System.currentTimeMillis() + interval());
|
||||
|
||||
int max = mgr.cfg().getInt("snowman.player.max-per-player", 3);
|
||||
long count = playerSnowmen(builder.getUniqueId()).size();
|
||||
builder.sendMessage(mgr.msg(count > max ? "snowman-limit" : "snowman-built", "%max%", String.valueOf(max)));
|
||||
}
|
||||
|
||||
private long interval() {
|
||||
return Math.max(10, mgr.cfg().getLong("snowman.player.gift-interval", 300)) * 1000L;
|
||||
}
|
||||
|
||||
private List<Snowman> playerSnowmen(UUID owner) {
|
||||
List<Snowman> list = new ArrayList<>();
|
||||
for (World w : Bukkit.getWorlds()) {
|
||||
for (Snowman s : w.getEntitiesByClass(Snowman.class)) {
|
||||
if (PLAYER.equals(type(s)) && owner.equals(owner(s))) list.add(s);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// ---------- Tick ----------
|
||||
|
||||
private void tick() {
|
||||
long now = System.currentTimeMillis();
|
||||
boolean particles = mgr.cfg().getBoolean("snowman.deco.particles", true);
|
||||
boolean walk = mgr.cfg().getBoolean("snowman.deco.walk", false);
|
||||
double radius = Math.max(2, mgr.cfg().getDouble("snowman.deco.walk-radius", 8));
|
||||
int max = mgr.cfg().getInt("snowman.player.max-per-player", 3);
|
||||
List<String> gifts = mgr.cfg().getStringList("snowman.player.gifts");
|
||||
|
||||
Map<UUID, List<Snowman>> byOwner = new HashMap<>();
|
||||
for (World w : Bukkit.getWorlds()) {
|
||||
for (Snowman s : w.getEntitiesByClass(Snowman.class)) {
|
||||
String t = type(s);
|
||||
if (DECO.equals(t)) {
|
||||
walkDeco(s, walk, radius);
|
||||
if (particles) w.spawnParticle(Particle.SNOWFLAKE, s.getLocation().add(0, 1.2, 0), 4, 0.5, 0.7, 0.5, 0.01);
|
||||
} else if (PLAYER.equals(t)) {
|
||||
UUID o = owner(s);
|
||||
if (o != null) byOwner.computeIfAbsent(o, k -> new ArrayList<>()).add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (List<Snowman> list : byOwner.values()) {
|
||||
// Aelteste zuerst: nur die ersten "max" Schneemaenner geben Geschenke
|
||||
list.sort(Comparator.comparingInt(Snowman::getTicksLived).reversed());
|
||||
for (int i = 0; i < list.size() && i < max; i++) {
|
||||
Snowman s = list.get(i);
|
||||
long due = nextGift.computeIfAbsent(s.getUniqueId(), k -> now + interval());
|
||||
if (now < due || gifts.isEmpty()) continue;
|
||||
nextGift.put(s.getUniqueId(), now + interval());
|
||||
ItemStack it = ChristmasManager.parseItem(gifts.get(ChristmasManager.RANDOM.nextInt(gifts.size())));
|
||||
if (it == null) continue;
|
||||
Location at = s.getLocation().add(0, 1.2, 0);
|
||||
s.getWorld().dropItem(at, it);
|
||||
s.getWorld().spawnParticle(Particle.SNOWFLAKE, at, 20, 0.4, 0.4, 0.4, 0.03);
|
||||
s.getWorld().playSound(at, Sound.BLOCK_SNOW_PLACE, 1f, 1.2f);
|
||||
}
|
||||
}
|
||||
nextGift.keySet().removeIf(id -> Bukkit.getEntity(id) == null && nextGift.size() > 500);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.SoundCategory;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.ItemFrame;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Weihnachtsflug: Am Weihnachtstag (und einen Tag danach) fliegt Santa von aufgestellter Socke zu
|
||||
* aufgestellter Socke und legt ein Geschenk darunter ab.
|
||||
*/
|
||||
final class StockingFlight {
|
||||
private final ChristmasManager mgr;
|
||||
private final StockingManager stockings;
|
||||
private BukkitTask scheduler;
|
||||
private BukkitTask flightTask;
|
||||
private LivingEntity santa;
|
||||
private long lastFlight;
|
||||
|
||||
// Ablauf eines Fluges
|
||||
private List<StockingManager.Placed> queue = new ArrayList<>();
|
||||
private int index;
|
||||
private int hover;
|
||||
private boolean leaving;
|
||||
private int leaveTicks;
|
||||
private Location leaveTarget;
|
||||
private float yaw;
|
||||
private int tickCount;
|
||||
|
||||
StockingFlight(ChristmasManager mgr, StockingManager stockings) {
|
||||
this.mgr = mgr;
|
||||
this.stockings = stockings;
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (scheduler != null) scheduler.cancel();
|
||||
scheduler = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::check, 600L, 20L * 30);
|
||||
}
|
||||
|
||||
void stopAll() {
|
||||
if (scheduler != null) scheduler.cancel();
|
||||
scheduler = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
boolean running() { return flightTask != null; }
|
||||
|
||||
// ---------- Zeitplan ----------
|
||||
|
||||
private LocalDate date(String key, String def) {
|
||||
LocalDate today = LocalDate.now(mgr.plugin().getZone());
|
||||
String[] p = mgr.cfg().getString(key, def).split("-");
|
||||
try {
|
||||
return LocalDate.of(today.getYear(), Integer.parseInt(p[0].trim()), Integer.parseInt(p[1].trim()));
|
||||
} catch (Exception e) {
|
||||
return LocalDate.of(today.getYear(), 12, 24);
|
||||
}
|
||||
}
|
||||
|
||||
/** Liegt heute im Flug-Zeitraum (Standard 24.12. bis 25.12.)? */
|
||||
boolean inWindow() {
|
||||
LocalDate today = LocalDate.now(mgr.plugin().getZone());
|
||||
return !today.isBefore(date("stocking-flight.from", "12-24")) && !today.isAfter(date("stocking-flight.until", "12-25"));
|
||||
}
|
||||
|
||||
private void check() {
|
||||
stockings.cleanup();
|
||||
if (flightTask != null) return;
|
||||
if (!mgr.cfg().getBoolean("stocking-flight.enable", true) || !mgr.cfg().getBoolean("stocking.enable", true)) return;
|
||||
if (!inWindow() || Bukkit.getOnlinePlayers().isEmpty()) return;
|
||||
long every = Math.max(1, mgr.cfg().getLong("stocking-flight.interval-minutes", 20)) * 60_000L;
|
||||
if (System.currentTimeMillis() - lastFlight < every) return;
|
||||
launch(false);
|
||||
}
|
||||
|
||||
// ---------- Flug ----------
|
||||
|
||||
/** Startet einen Flug; force = auch ausserhalb des Zeitraums und trotz heutiger Lieferung (Admin-Test). */
|
||||
boolean launch(boolean force) {
|
||||
if (flightTask != null) return false;
|
||||
String today = LocalDate.now(mgr.plugin().getZone()).toString();
|
||||
|
||||
// Socken in geladenen Chunks, nach Welt gruppiert; die Welt mit den meisten gewinnt
|
||||
java.util.Map<String, List<StockingManager.Placed>> byWorld = new java.util.HashMap<>();
|
||||
for (StockingManager.Placed pl : new ArrayList<>(stockings.placed().values())) {
|
||||
World w = Bukkit.getWorld(pl.world);
|
||||
if (w == null || !w.isChunkLoaded(pl.x >> 4, pl.z >> 4)) continue;
|
||||
ItemFrame f = stockings.frameOf(pl);
|
||||
if (f == null) continue;
|
||||
if (!force && (today.equals(pl.lastDay) || pl.presentKey != null)) continue;
|
||||
byWorld.computeIfAbsent(pl.world, k -> new ArrayList<>()).add(pl);
|
||||
}
|
||||
List<StockingManager.Placed> best = new ArrayList<>();
|
||||
for (var l : byWorld.values()) if (l.size() > best.size()) best = l;
|
||||
if (best.isEmpty()) return false;
|
||||
|
||||
// Reihenfolge: immer zur naechsten Socke
|
||||
int max = Math.max(1, mgr.cfg().getInt("stocking-flight.max-stockings-per-flight", 12));
|
||||
List<StockingManager.Placed> pool = new ArrayList<>(best);
|
||||
queue = new ArrayList<>();
|
||||
StockingManager.Placed cur = pool.remove(ChristmasManager.RANDOM.nextInt(pool.size()));
|
||||
queue.add(cur);
|
||||
while (!pool.isEmpty() && queue.size() < max) {
|
||||
final StockingManager.Placed from = cur;
|
||||
StockingManager.Placed next = pool.stream().min(java.util.Comparator.comparingDouble(
|
||||
x -> dist2(from, x))).get();
|
||||
pool.remove(next);
|
||||
queue.add(next);
|
||||
cur = next;
|
||||
}
|
||||
|
||||
World w = Bukkit.getWorld(queue.get(0).world);
|
||||
Location first = hoverPoint(queue.get(0));
|
||||
double ang = ChristmasManager.RANDOM.nextDouble() * Math.PI * 2;
|
||||
Location spawn = first.clone().add(Math.cos(ang) * 45, 22, Math.sin(ang) * 45);
|
||||
try {
|
||||
santa = mgr.santa().createModel(spawn);
|
||||
santa.setGravity(false);
|
||||
santa.setInvulnerable(true);
|
||||
santa.setSilent(true);
|
||||
santa.setPersistent(false);
|
||||
} catch (Throwable t) {
|
||||
mgr.plugin().getLogger().warning("Santa-Flug konnte nicht gestartet werden: " + t);
|
||||
finish();
|
||||
return false;
|
||||
}
|
||||
lastFlight = System.currentTimeMillis();
|
||||
index = 0;
|
||||
hover = 0;
|
||||
leaving = false;
|
||||
tickCount = 0;
|
||||
yaw = spawn.getYaw();
|
||||
for (Player p : w.getPlayers()) p.sendMessage(mgr.msg("stocking-flight-started"));
|
||||
w.playSound(spawn, Sound.BLOCK_AMETHYST_BLOCK_CHIME, SoundCategory.RECORDS, 4f, 1.4f);
|
||||
flightTask = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::tick, 1L, 1L);
|
||||
return true;
|
||||
}
|
||||
|
||||
private double dist2(StockingManager.Placed a, StockingManager.Placed b) {
|
||||
double dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
private Location hoverPoint(StockingManager.Placed pl) {
|
||||
return new Location(Bukkit.getWorld(pl.world), pl.x + 0.5, pl.y + 2.2, pl.z + 0.5);
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
if (santa == null || !santa.isValid()) { finish(); return; }
|
||||
World w = santa.getWorld();
|
||||
Location pos = santa.getLocation();
|
||||
tickCount++;
|
||||
|
||||
// Schweif und Glocken
|
||||
w.spawnParticle(Particle.END_ROD, pos.clone().add(0, 1, 0), 2, 0.3, 0.3, 0.3, 0.01);
|
||||
w.spawnParticle(Particle.SNOWFLAKE, pos.clone().add(0, 0.8, 0), 4, 0.4, 0.4, 0.4, 0.02);
|
||||
if (tickCount % 16 == 0) {
|
||||
float pitch = (float) Math.pow(2.0, (ChristmasManager.RANDOM.nextInt(8) - 4) / 12.0);
|
||||
w.playSound(pos, Sound.BLOCK_NOTE_BLOCK_BELL, SoundCategory.RECORDS, 2.5f, pitch);
|
||||
}
|
||||
|
||||
if (leaving) {
|
||||
leaveTicks--;
|
||||
moveToward(pos, leaveTarget, 0.9);
|
||||
if (leaveTicks <= 0) { finish(); }
|
||||
return;
|
||||
}
|
||||
|
||||
// Naechste noch vorhandene Socke suchen
|
||||
while (index < queue.size() && stockings.frameOf(queue.get(index)) == null) index++;
|
||||
if (index >= queue.size()) { startLeaving(pos); return; }
|
||||
|
||||
StockingManager.Placed pl = queue.get(index);
|
||||
Location target = hoverPoint(pl);
|
||||
if (hover == 0) {
|
||||
double d = pos.distance(target);
|
||||
double speed = Math.max(0.2, mgr.cfg().getDouble("stocking-flight.speed", 0.7));
|
||||
moveToward(pos, target, Math.min(speed, Math.max(0.08, d * 0.12)));
|
||||
if (d < 0.5) hover = 1;
|
||||
return;
|
||||
}
|
||||
// Schweben und Geschenk ablegen
|
||||
hover++;
|
||||
if (hover == 14) placePresent(pl);
|
||||
if (hover > 36) { hover = 0; index++; }
|
||||
}
|
||||
|
||||
private void moveToward(Location pos, Location target, double step) {
|
||||
double dx = target.getX() - pos.getX(), dy = target.getY() - pos.getY(), dz = target.getZ() - pos.getZ();
|
||||
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (len < 1e-6) return;
|
||||
double f = Math.min(1.0, step / len);
|
||||
double nx = pos.getX() + dx * f, ny = pos.getY() + dy * f, nz = pos.getZ() + dz * f;
|
||||
double hx = target.getX() - pos.getX(), hz = target.getZ() - pos.getZ();
|
||||
if (hx * hx + hz * hz > 0.25) {
|
||||
float want = (float) Math.toDegrees(Math.atan2(-hx, hz));
|
||||
float diff = ((want - yaw + 540f) % 360f) - 180f;
|
||||
yaw += Math.max(-14f, Math.min(14f, diff));
|
||||
}
|
||||
santa.teleport(new Location(pos.getWorld(), nx, ny, nz, yaw, 0f));
|
||||
}
|
||||
|
||||
private void startLeaving(Location pos) {
|
||||
leaving = true;
|
||||
leaveTicks = 70;
|
||||
double ang = ChristmasManager.RANDOM.nextDouble() * Math.PI * 2;
|
||||
leaveTarget = pos.clone().add(Math.cos(ang) * 60, 35, Math.sin(ang) * 60);
|
||||
}
|
||||
|
||||
private void placePresent(StockingManager.Placed pl) {
|
||||
World w = Bukkit.getWorld(pl.world);
|
||||
if (w == null) return;
|
||||
String today = LocalDate.now(mgr.plugin().getZone()).toString();
|
||||
|
||||
// Geschenk unter die Socke: freier Block darunter (bis zu 2 Bloecke tiefer)
|
||||
Block frame = w.getBlockAt(pl.x, pl.y, pl.z);
|
||||
Block spot = null;
|
||||
for (int i = 1; i <= 2 && spot == null; i++) {
|
||||
Block b = frame.getRelative(0, -i, 0);
|
||||
if (b.getType().isAir()) spot = b;
|
||||
else if (b.getType().isSolid()) break;
|
||||
}
|
||||
if (spot == null) return;
|
||||
|
||||
List<String> skins = mgr.cfg().getStringList("magic-tree.presents");
|
||||
if (skins.isEmpty()) {
|
||||
spot.setType(Material.CHEST, false);
|
||||
} else {
|
||||
mgr.trees().setSkin(spot, skins.get(ChristmasManager.RANDOM.nextInt(skins.size())));
|
||||
}
|
||||
stockings.registerPresent(pl, spot);
|
||||
pl.lastDay = today;
|
||||
stockings.save();
|
||||
|
||||
Location at = spot.getLocation().add(0.5, 0.6, 0.5);
|
||||
w.spawnParticle(Particle.END_ROD, at, 25, 0.3, 0.3, 0.3, 0.05);
|
||||
w.spawnParticle(Particle.HAPPY_VILLAGER, at, 12, 0.3, 0.3, 0.3, 0);
|
||||
w.playSound(at, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.3f);
|
||||
w.playSound(at, Sound.BLOCK_NOTE_BLOCK_CHIME, SoundCategory.RECORDS, 1.5f, 1.5f);
|
||||
|
||||
Player owner = Bukkit.getPlayer(pl.owner);
|
||||
if (owner != null) {
|
||||
owner.sendMessage(mgr.msg("stocking-present-placed",
|
||||
"%x%", String.valueOf(spot.getX()), "%y%", String.valueOf(spot.getY()), "%z%", String.valueOf(spot.getZ())));
|
||||
}
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
if (flightTask != null) flightTask.cancel();
|
||||
flightTask = null;
|
||||
if (santa != null) {
|
||||
try { santa.remove(); } catch (Throwable ignored) { }
|
||||
santa = null;
|
||||
}
|
||||
queue = new ArrayList<>();
|
||||
leaving = false;
|
||||
}
|
||||
|
||||
/** Fuer Aufraeumen: ID der Socke zum Flug (nur zur Info). */
|
||||
UUID currentSock() { return index < queue.size() ? queue.get(index).id : null; }
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.ItemFrame;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.LeatherArmorMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.util.RayTraceResult;
|
||||
import java.util.List;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Kamin-Geschenke: Socke aufhaengen, am 25.12. liegt etwas darin. */
|
||||
public final class StockingManager {
|
||||
private static final class Data {
|
||||
int hungYear;
|
||||
int claimedYear;
|
||||
}
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private final File file;
|
||||
private final Map<UUID, Data> data = new HashMap<>();
|
||||
private BukkitTask task;
|
||||
|
||||
/** Aufgestellte Socke (Item-Rahmen an der Wand). */
|
||||
static final class Placed {
|
||||
UUID id;
|
||||
UUID owner;
|
||||
String world;
|
||||
int x, y, z;
|
||||
String presentKey; // noch nicht abgeholtes Geschenk unter der Socke
|
||||
String lastDay = ""; // Datum der letzten Lieferung
|
||||
}
|
||||
|
||||
private final Map<UUID, Placed> placed = new HashMap<>();
|
||||
private final Map<String, UUID> presents = new HashMap<>(); // Blockschluessel -> Besitzer
|
||||
private final NamespacedKey sockKey;
|
||||
private final StockingFlight flight;
|
||||
|
||||
StockingManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.file = new File(mgr.plugin().getDataFolder(), "stockings.yml");
|
||||
this.sockKey = new NamespacedKey(mgr.plugin(), "stocking_frame");
|
||||
this.flight = new StockingFlight(mgr, this);
|
||||
}
|
||||
|
||||
StockingFlight flight() { return flight; }
|
||||
|
||||
void load() {
|
||||
data.clear();
|
||||
if (file.exists()) {
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(file);
|
||||
ConfigurationSection s = y.getConfigurationSection("players");
|
||||
if (s != null) {
|
||||
for (String k : s.getKeys(false)) {
|
||||
try {
|
||||
Data d = new Data();
|
||||
d.hungYear = s.getInt(k + ".hung");
|
||||
d.claimedYear = s.getInt(k + ".claimed");
|
||||
data.put(UUID.fromString(k), d);
|
||||
} catch (IllegalArgumentException ignored) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
placed.clear();
|
||||
presents.clear();
|
||||
if (file.exists()) {
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(file);
|
||||
ConfigurationSection ps = y.getConfigurationSection("placed");
|
||||
if (ps != null) {
|
||||
for (String k : ps.getKeys(false)) {
|
||||
try {
|
||||
Placed pl = new Placed();
|
||||
pl.id = UUID.fromString(k);
|
||||
pl.owner = UUID.fromString(ps.getString(k + ".owner", ""));
|
||||
pl.world = ps.getString(k + ".world", "");
|
||||
pl.x = ps.getInt(k + ".x");
|
||||
pl.y = ps.getInt(k + ".y");
|
||||
pl.z = ps.getInt(k + ".z");
|
||||
pl.presentKey = ps.getString(k + ".present");
|
||||
pl.lastDay = ps.getString(k + ".last-day", "");
|
||||
placed.put(pl.id, pl);
|
||||
} catch (IllegalArgumentException ignored) { }
|
||||
}
|
||||
}
|
||||
ConfigurationSection gs = y.getConfigurationSection("presents");
|
||||
if (gs != null) {
|
||||
for (String k : gs.getKeys(false)) {
|
||||
try { presents.put(k.replace('|', ';'), UUID.fromString(gs.getString(k, ""))); }
|
||||
catch (IllegalArgumentException ignored) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
stop();
|
||||
flight.start();
|
||||
task = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), () -> {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) tryDeliver(p, false);
|
||||
}, 400L, 20L * 60);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
flight.stopAll();
|
||||
}
|
||||
|
||||
void save() {
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
for (var e : data.entrySet()) {
|
||||
y.set("players." + e.getKey() + ".hung", e.getValue().hungYear);
|
||||
y.set("players." + e.getKey() + ".claimed", e.getValue().claimedYear);
|
||||
}
|
||||
for (Placed pl : placed.values()) {
|
||||
String b = "placed." + pl.id;
|
||||
y.set(b + ".owner", pl.owner.toString());
|
||||
y.set(b + ".world", pl.world);
|
||||
y.set(b + ".x", pl.x);
|
||||
y.set(b + ".y", pl.y);
|
||||
y.set(b + ".z", pl.z);
|
||||
y.set(b + ".present", pl.presentKey);
|
||||
y.set(b + ".last-day", pl.lastDay);
|
||||
}
|
||||
for (var e : presents.entrySet()) y.set("presents." + e.getKey().replace(';', '|'), e.getValue().toString());
|
||||
try {
|
||||
y.save(file);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("stockings.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Datumslogik ----------
|
||||
|
||||
private LocalDate date(String key, String def) {
|
||||
LocalDate today = LocalDate.now(mgr.plugin().getZone());
|
||||
String[] p = mgr.cfg().getString(key, def).split("-");
|
||||
try {
|
||||
return LocalDate.of(today.getYear(), Integer.parseInt(p[0].trim()), Integer.parseInt(p[1].trim()));
|
||||
} catch (Exception e) {
|
||||
return LocalDate.of(today.getYear(), 12, 25);
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate today() { return LocalDate.now(mgr.plugin().getZone()); }
|
||||
|
||||
// ---------- Aktionen ----------
|
||||
|
||||
/** /xmas stocking: aufhaengen oder (ab dem Tag) abholen. */
|
||||
public void use(Player p) {
|
||||
if (!mgr.cfg().getBoolean("stocking.enable", true)) return;
|
||||
LocalDate today = today();
|
||||
LocalDate from = date("stocking.hang-from", "12-01");
|
||||
LocalDate open = date("stocking.open-on", "12-25");
|
||||
int year = today.getYear();
|
||||
Data d = data.computeIfAbsent(p.getUniqueId(), k -> new Data());
|
||||
|
||||
if (today.isBefore(open)) {
|
||||
if (today.isBefore(from)) {
|
||||
p.sendMessage(mgr.msg("stocking-too-early", "%from%", from.getDayOfMonth() + "." + from.getMonthValue() + "."));
|
||||
return;
|
||||
}
|
||||
if (d.hungYear == year) { p.sendMessage(mgr.msg("stocking-already", "%date%", open.getDayOfMonth() + "." + open.getMonthValue() + ".")); return; }
|
||||
d.hungYear = year;
|
||||
save();
|
||||
p.sendMessage(mgr.msg("stocking-hung", "%date%", open.getDayOfMonth() + "." + open.getMonthValue() + "."));
|
||||
return;
|
||||
}
|
||||
if (d.hungYear != year) { p.sendMessage(mgr.msg("stocking-nothing")); return; }
|
||||
if (d.claimedYear == year) { p.sendMessage(mgr.msg("stocking-claimed")); return; }
|
||||
deliver(p, d);
|
||||
}
|
||||
|
||||
/** Automatisch: nach dem Oeffnungstag bekommt jeder mit aufgehaengter Socke sein Geschenk. */
|
||||
public void tryDeliver(Player p, boolean announceIfNone) {
|
||||
if (!mgr.cfg().getBoolean("stocking.enable", true)) return;
|
||||
LocalDate today = today();
|
||||
if (today.isBefore(date("stocking.open-on", "12-25")) || today.isAfter(date("stocking.until", "12-31"))) return;
|
||||
Data d = data.get(p.getUniqueId());
|
||||
if (d == null || d.hungYear != today.getYear() || d.claimedYear == today.getYear()) return;
|
||||
deliver(p, d);
|
||||
}
|
||||
|
||||
private void deliver(Player p, Data d) {
|
||||
d.claimedYear = today().getYear();
|
||||
save();
|
||||
for (var reward : mgr.cfg().getMapList("stocking.rewards")) ChristmasExtras.applyMap(p, reward);
|
||||
p.sendMessage(mgr.msg("stocking-received"));
|
||||
p.getWorld().spawnParticle(org.bukkit.Particle.SNOWFLAKE, p.getLocation().add(0, 1.5, 0), 30, 0.5, 0.5, 0.5, 0.03);
|
||||
}
|
||||
|
||||
// ---------- Aufgestellte Socken ----------
|
||||
|
||||
Map<UUID, Placed> placed() { return placed; }
|
||||
|
||||
private static String key(Block b) {
|
||||
return b.getWorld().getName() + ";" + b.getX() + ";" + b.getY() + ";" + b.getZ();
|
||||
}
|
||||
|
||||
private ItemStack sockItem() {
|
||||
ItemStack it = new ItemStack(Material.LEATHER_BOOTS);
|
||||
if (it.getItemMeta() instanceof LeatherArmorMeta m) {
|
||||
m.setColor(Color.RED);
|
||||
m.setDisplayName(ChristmasManager.color("&cWeihnachtssocke"));
|
||||
it.setItemMeta(m);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
/** /xmas stocking place: haengt eine Socke an die Wand, auf die der Spieler schaut. */
|
||||
public void place(Player p) {
|
||||
if (!mgr.cfg().getBoolean("stocking.enable", true)) return;
|
||||
int max = mgr.cfg().getInt("stocking.max-placed-per-player", 3);
|
||||
long mine = placed.values().stream().filter(x -> x.owner.equals(p.getUniqueId())).count();
|
||||
if (mine >= max && !p.hasPermission("xmas.admin")) {
|
||||
p.sendMessage(mgr.msg("stocking-place-limit", "%max%", String.valueOf(max)));
|
||||
return;
|
||||
}
|
||||
RayTraceResult r = p.rayTraceBlocks(5);
|
||||
if (r == null || r.getHitBlock() == null || r.getHitBlockFace() == null) {
|
||||
p.sendMessage(mgr.msg("stocking-place-usage"));
|
||||
return;
|
||||
}
|
||||
BlockFace face = r.getHitBlockFace();
|
||||
if (face == BlockFace.UP || face == BlockFace.DOWN) { p.sendMessage(mgr.msg("stocking-place-usage")); return; }
|
||||
Block at = r.getHitBlock().getRelative(face);
|
||||
if (!at.getType().isAir()) { p.sendMessage(mgr.msg("stocking-place-usage")); return; }
|
||||
for (Placed o : placed.values()) {
|
||||
if (o.world.equals(at.getWorld().getName()) && o.x == at.getX() && o.y == at.getY() && o.z == at.getZ()) {
|
||||
p.sendMessage(mgr.msg("stocking-place-usage"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
ItemFrame f = at.getWorld().spawn(at.getLocation(), ItemFrame.class, fr -> {
|
||||
fr.setFacingDirection(face, true);
|
||||
fr.setVisible(false);
|
||||
fr.setItem(sockItem(), false);
|
||||
fr.setFixed(true);
|
||||
fr.setInvulnerable(true);
|
||||
fr.setPersistent(true);
|
||||
fr.getPersistentDataContainer().set(sockKey, PersistentDataType.STRING, p.getUniqueId().toString());
|
||||
});
|
||||
Placed pl = new Placed();
|
||||
pl.id = f.getUniqueId();
|
||||
pl.owner = p.getUniqueId();
|
||||
pl.world = at.getWorld().getName();
|
||||
pl.x = at.getX();
|
||||
pl.y = at.getY();
|
||||
pl.z = at.getZ();
|
||||
placed.put(pl.id, pl);
|
||||
save();
|
||||
at.getWorld().spawnParticle(Particle.SNOWFLAKE, at.getLocation().add(0.5, 0.5, 0.5), 20, 0.3, 0.3, 0.3, 0.02);
|
||||
p.sendMessage(mgr.msg("stocking-placed"));
|
||||
}
|
||||
|
||||
/** /xmas stocking remove: entfernt die naechste eigene Socke (Admins: jede). */
|
||||
public void removeNearest(Player p) {
|
||||
ItemFrame best = null;
|
||||
double bd = Double.MAX_VALUE;
|
||||
for (Entity e : p.getNearbyEntities(6, 6, 6)) {
|
||||
if (!(e instanceof ItemFrame f) || !placed.containsKey(f.getUniqueId())) continue;
|
||||
Placed pl = placed.get(f.getUniqueId());
|
||||
if (!pl.owner.equals(p.getUniqueId()) && !p.hasPermission("xmas.admin")) continue;
|
||||
double d = f.getLocation().distanceSquared(p.getLocation());
|
||||
if (d < bd) { bd = d; best = f; }
|
||||
}
|
||||
if (best == null) { p.sendMessage(mgr.msg("stocking-none-near")); return; }
|
||||
Placed pl = placed.remove(best.getUniqueId());
|
||||
best.remove();
|
||||
if (pl != null && pl.presentKey != null) presents.remove(pl.presentKey);
|
||||
save();
|
||||
p.sendMessage(mgr.msg("stocking-removed"));
|
||||
}
|
||||
|
||||
/** Entfernt Eintraege, deren Rahmen es nicht mehr gibt (z.B. im Kreativmodus zerstoert). */
|
||||
void cleanup() {
|
||||
boolean changed = false;
|
||||
for (var it = placed.values().iterator(); it.hasNext(); ) {
|
||||
Placed pl = it.next();
|
||||
World w = Bukkit.getWorld(pl.world);
|
||||
if (w == null || !w.isChunkLoaded(pl.x >> 4, pl.z >> 4)) continue;
|
||||
if (frameOf(pl) == null) { it.remove(); changed = true; }
|
||||
}
|
||||
if (changed) save();
|
||||
}
|
||||
|
||||
/** Rahmen der Socke (null, wenn nicht geladen/vorhanden). */
|
||||
ItemFrame frameOf(Placed pl) {
|
||||
Entity e = Bukkit.getEntity(pl.id);
|
||||
return e instanceof ItemFrame f && f.isValid() ? f : null;
|
||||
}
|
||||
|
||||
// ---------- Geschenke unter der Socke ----------
|
||||
|
||||
public boolean isPresentBlock(Block b) { return presents.containsKey(key(b)); }
|
||||
|
||||
void registerPresent(Placed pl, Block b) {
|
||||
String k = key(b);
|
||||
presents.put(k, pl.owner);
|
||||
pl.presentKey = k;
|
||||
}
|
||||
|
||||
/** Spieler oeffnet ein Socken-Geschenk (Rechtsklick oder Abbauen). */
|
||||
public void openPresent(Player p, Block b) {
|
||||
String k = key(b);
|
||||
UUID owner = presents.get(k);
|
||||
if (owner == null) return;
|
||||
if (mgr.cfg().getBoolean("stocking-flight.owner-only", true)
|
||||
&& !owner.equals(p.getUniqueId()) && !p.hasPermission("xmas.admin")) {
|
||||
p.sendMessage(mgr.msg("stocking-present-notyours"));
|
||||
return;
|
||||
}
|
||||
presents.remove(k);
|
||||
for (Placed pl : placed.values()) if (k.equals(pl.presentKey)) pl.presentKey = null;
|
||||
b.setType(Material.AIR, false);
|
||||
World w = b.getWorld();
|
||||
w.spawnParticle(Particle.END_ROD, b.getLocation().add(0.5, 0.5, 0.5), 20, 0.3, 0.3, 0.3, 0.05);
|
||||
w.playSound(b.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.4f);
|
||||
List<Map<?, ?>> rewards = mgr.cfg().getMapList("stocking-flight.rewards");
|
||||
if (rewards.isEmpty()) rewards = mgr.cfg().getMapList("stocking.rewards");
|
||||
for (Map<?, ?> reward : rewards) ChristmasExtras.applyMap(p, reward);
|
||||
p.sendMessage(mgr.msg("stocking-present-opened"));
|
||||
save();
|
||||
}
|
||||
|
||||
/** Geschenke unter Socken sind vor Zerstoerung geschuetzt. */
|
||||
public boolean isProtected(Block b) { return isPresentBlock(b); }
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import de.mviper.adventskalender.RewardUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Wichteln: Anmelden, zufaellig ziehen, Geschenk ablegen, am Ende verteilen. */
|
||||
public final class WichtelnManager {
|
||||
public enum State { CLOSED, OPEN, DRAWN, DELIVERED }
|
||||
|
||||
private record Pending(String from, ItemStack item) {}
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private final File file;
|
||||
private State state = State.CLOSED;
|
||||
private final Map<UUID, String> participants = new LinkedHashMap<>();
|
||||
private final Map<UUID, UUID> assign = new LinkedHashMap<>();
|
||||
private final Map<UUID, ItemStack> gifts = new LinkedHashMap<>();
|
||||
private final Map<UUID, List<Pending>> pending = new LinkedHashMap<>();
|
||||
|
||||
WichtelnManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.file = new File(mgr.plugin().getDataFolder(), "wichteln.yml");
|
||||
}
|
||||
|
||||
public State state() { return state; }
|
||||
public int participantCount() { return participants.size(); }
|
||||
public boolean joined(UUID id) { return participants.containsKey(id); }
|
||||
|
||||
public String targetName(UUID giver) {
|
||||
UUID t = assign.get(giver);
|
||||
return t == null ? "" : participants.getOrDefault(t, "");
|
||||
}
|
||||
|
||||
// ---------- Phasen ----------
|
||||
|
||||
public boolean open() {
|
||||
if (state != State.CLOSED && state != State.DELIVERED) return false;
|
||||
reset();
|
||||
state = State.OPEN;
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean join(Player p) {
|
||||
if (state != State.OPEN) return false;
|
||||
participants.put(p.getUniqueId(), p.getName());
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean leave(Player p) {
|
||||
if (state != State.OPEN) return false;
|
||||
boolean had = participants.remove(p.getUniqueId()) != null;
|
||||
save();
|
||||
return had;
|
||||
}
|
||||
|
||||
/** Zieht zufaellig, sodass niemand sich selbst beschenkt. false bei zu wenigen Teilnehmern. */
|
||||
public boolean draw() {
|
||||
if (state != State.OPEN || participants.size() < 3) return false;
|
||||
List<UUID> givers = new ArrayList<>(participants.keySet());
|
||||
List<UUID> receivers = new ArrayList<>(givers);
|
||||
boolean ok = false;
|
||||
for (int attempt = 0; attempt < 2000 && !ok; attempt++) {
|
||||
Collections.shuffle(receivers, ChristmasManager.RANDOM);
|
||||
ok = true;
|
||||
for (int i = 0; i < givers.size(); i++) {
|
||||
if (givers.get(i).equals(receivers.get(i))) { ok = false; break; }
|
||||
}
|
||||
}
|
||||
if (!ok) return false;
|
||||
assign.clear();
|
||||
for (int i = 0; i < givers.size(); i++) assign.put(givers.get(i), receivers.get(i));
|
||||
state = State.DRAWN;
|
||||
save();
|
||||
for (UUID id : givers) {
|
||||
Player p = Bukkit.getPlayer(id);
|
||||
if (p != null) p.sendMessage(mgr.msg("wi-who", "%name%", targetName(id)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Legt das Item in der Hand als Geschenk ab (ersetzt ein vorheriges, das zurueckgegeben wird). */
|
||||
public String setGift(Player p) {
|
||||
if (state != State.DRAWN) return "wi-wrong-state";
|
||||
if (!assign.containsKey(p.getUniqueId())) return "wi-not-joined";
|
||||
ItemStack hand = p.getInventory().getItemInMainHand();
|
||||
if (hand == null || hand.getType().isAir()) return "wi-gift-empty";
|
||||
ItemStack old = gifts.put(p.getUniqueId(), hand.clone());
|
||||
p.getInventory().setItemInMainHand(null);
|
||||
if (old != null) RewardUtil.give(p, old);
|
||||
save();
|
||||
return "wi-gift-saved";
|
||||
}
|
||||
|
||||
public int giftCount() { return gifts.size(); }
|
||||
|
||||
/** Verteilt alle Geschenke; Offline-Spieler bekommen ihres beim naechsten Join. */
|
||||
public int deliver() {
|
||||
if (state != State.DRAWN) return -1;
|
||||
boolean reveal = mgr.cfg().getBoolean("wichteln.reveal-giver", false);
|
||||
int n = 0;
|
||||
for (var e : gifts.entrySet()) {
|
||||
UUID receiver = assign.get(e.getKey());
|
||||
if (receiver == null) continue;
|
||||
String from = reveal ? participants.getOrDefault(e.getKey(), "?") : "";
|
||||
pending.computeIfAbsent(receiver, k -> new ArrayList<>()).add(new Pending(from, e.getValue()));
|
||||
n++;
|
||||
}
|
||||
gifts.clear();
|
||||
state = State.DELIVERED;
|
||||
save();
|
||||
for (Player p : Bukkit.getOnlinePlayers()) onJoin(p);
|
||||
return n;
|
||||
}
|
||||
|
||||
/** Uebergibt wartende Geschenke. */
|
||||
public void onJoin(Player p) {
|
||||
List<Pending> list = pending.remove(p.getUniqueId());
|
||||
if (list == null) return;
|
||||
for (Pending pe : list) {
|
||||
RewardUtil.give(p, pe.item());
|
||||
p.sendMessage(mgr.msg(pe.from().isEmpty() ? "wi-received" : "wi-received-from", "%from%", pe.from()));
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
participants.clear();
|
||||
assign.clear();
|
||||
gifts.clear();
|
||||
pending.clear();
|
||||
state = State.CLOSED;
|
||||
save();
|
||||
}
|
||||
|
||||
// ---------- Persistenz ----------
|
||||
|
||||
void load() {
|
||||
if (!file.exists()) return;
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(file);
|
||||
try { state = State.valueOf(y.getString("state", "CLOSED")); } catch (IllegalArgumentException e) { state = State.CLOSED; }
|
||||
participants.clear(); assign.clear(); gifts.clear(); pending.clear();
|
||||
ConfigurationSection ps = y.getConfigurationSection("participants");
|
||||
if (ps != null) for (String k : ps.getKeys(false)) participants.put(UUID.fromString(k), ps.getString(k, "?"));
|
||||
ConfigurationSection as = y.getConfigurationSection("assign");
|
||||
if (as != null) for (String k : as.getKeys(false)) assign.put(UUID.fromString(k), UUID.fromString(as.getString(k, "")));
|
||||
ConfigurationSection gs = y.getConfigurationSection("gifts");
|
||||
if (gs != null) for (String k : gs.getKeys(false)) { ItemStack it = gs.getItemStack(k); if (it != null) gifts.put(UUID.fromString(k), it); }
|
||||
ConfigurationSection pe = y.getConfigurationSection("pending");
|
||||
if (pe != null) {
|
||||
for (String k : pe.getKeys(false)) {
|
||||
List<Pending> list = new ArrayList<>();
|
||||
for (Map<?, ?> m : pe.getMapList(k)) {
|
||||
Object item = m.get("item");
|
||||
if (item instanceof ItemStack is) list.add(new Pending(String.valueOf(m.get("from")), is));
|
||||
}
|
||||
pending.put(UUID.fromString(k), list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void save() {
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
y.set("state", state.name());
|
||||
participants.forEach((k, v) -> y.set("participants." + k, v));
|
||||
assign.forEach((k, v) -> y.set("assign." + k, v.toString()));
|
||||
gifts.forEach((k, v) -> y.set("gifts." + k, v));
|
||||
pending.forEach((k, list) -> {
|
||||
List<Map<String, Object>> maps = new ArrayList<>();
|
||||
for (Pending p : list) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("from", p.from());
|
||||
m.put("item", p.item());
|
||||
maps.add(m);
|
||||
}
|
||||
y.set("pending." + k, maps);
|
||||
});
|
||||
try {
|
||||
y.save(file);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("wichteln.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import de.mviper.adventskalender.RewardUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Wunschzettel: Spieler schreiben Wuensche, Admins sehen sie in einem Menue und erfuellen sie. */
|
||||
public final class WishManager {
|
||||
|
||||
private static final class Wish {
|
||||
int id;
|
||||
UUID owner;
|
||||
String name;
|
||||
String text;
|
||||
long created;
|
||||
boolean done;
|
||||
boolean notified;
|
||||
ItemStack gift;
|
||||
}
|
||||
|
||||
public static final class Menu implements InventoryHolder {
|
||||
Inventory inv;
|
||||
int page;
|
||||
final Map<Integer, Integer> slotToId = new LinkedHashMap<>();
|
||||
@Override public Inventory getInventory() { return inv; }
|
||||
}
|
||||
|
||||
private static final int PAGE_SIZE = 45;
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private final File file;
|
||||
private final Map<Integer, Wish> wishes = new LinkedHashMap<>();
|
||||
private int nextId = 1;
|
||||
|
||||
WishManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.file = new File(mgr.plugin().getDataFolder(), "wishes.yml");
|
||||
}
|
||||
|
||||
// ---------- Persistenz ----------
|
||||
|
||||
void load() {
|
||||
wishes.clear();
|
||||
if (!file.exists()) return;
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(file);
|
||||
nextId = y.getInt("next-id", 1);
|
||||
ConfigurationSection s = y.getConfigurationSection("wishes");
|
||||
if (s == null) return;
|
||||
for (String k : s.getKeys(false)) {
|
||||
try {
|
||||
Wish w = new Wish();
|
||||
w.id = Integer.parseInt(k);
|
||||
w.owner = UUID.fromString(s.getString(k + ".owner", ""));
|
||||
w.name = s.getString(k + ".name", "?");
|
||||
w.text = s.getString(k + ".text", "");
|
||||
w.created = s.getLong(k + ".created");
|
||||
w.done = s.getBoolean(k + ".done");
|
||||
w.notified = s.getBoolean(k + ".notified");
|
||||
w.gift = s.getItemStack(k + ".gift");
|
||||
wishes.put(w.id, w);
|
||||
} catch (IllegalArgumentException ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
void save() {
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
y.set("next-id", nextId);
|
||||
for (Wish w : wishes.values()) {
|
||||
String p = "wishes." + w.id;
|
||||
y.set(p + ".owner", w.owner.toString());
|
||||
y.set(p + ".name", w.name);
|
||||
y.set(p + ".text", w.text);
|
||||
y.set(p + ".created", w.created);
|
||||
y.set(p + ".done", w.done);
|
||||
y.set(p + ".notified", w.notified);
|
||||
if (w.gift != null) y.set(p + ".gift", w.gift);
|
||||
}
|
||||
try {
|
||||
y.save(file);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("wishes.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Spieler ----------
|
||||
|
||||
private List<Wish> openOf(UUID owner) {
|
||||
List<Wish> list = new ArrayList<>();
|
||||
for (Wish w : wishes.values()) if (!w.done && w.owner.equals(owner)) list.add(w);
|
||||
return list;
|
||||
}
|
||||
|
||||
/** @return Nachrichten-Schluessel */
|
||||
public String add(Player p, String text) {
|
||||
text = text.trim();
|
||||
int max = Math.max(10, mgr.cfg().getInt("wishes.max-length", 120));
|
||||
if (text.isEmpty()) return "wish-usage";
|
||||
if (text.length() > max) return "wish-too-long";
|
||||
if (openOf(p.getUniqueId()).size() >= Math.max(1, mgr.cfg().getInt("wishes.max-open-per-player", 3))) return "wish-limit";
|
||||
Wish w = new Wish();
|
||||
w.id = nextId++;
|
||||
w.owner = p.getUniqueId();
|
||||
w.name = p.getName();
|
||||
w.text = text.replace('&', '+'); // keine Farbcodes von Spielern
|
||||
w.created = System.currentTimeMillis();
|
||||
wishes.put(w.id, w);
|
||||
save();
|
||||
for (Player admin : Bukkit.getOnlinePlayers()) {
|
||||
if (admin.hasPermission("xmas.admin")) admin.sendMessage(mgr.msg("wish-admin-new", "%player%", p.getName()));
|
||||
}
|
||||
return "wish-saved";
|
||||
}
|
||||
|
||||
public void list(Player p) {
|
||||
List<Wish> open = openOf(p.getUniqueId());
|
||||
if (open.isEmpty()) { p.sendMessage(mgr.msg("wish-empty")); return; }
|
||||
p.sendMessage(mgr.msg("wish-list-header"));
|
||||
for (Wish w : open) p.sendMessage(mgr.raw("wish-list-line").replace("%text%", w.text));
|
||||
}
|
||||
|
||||
public int clearOwn(Player p) {
|
||||
List<Wish> open = openOf(p.getUniqueId());
|
||||
for (Wish w : open) wishes.remove(w.id);
|
||||
if (!open.isEmpty()) save();
|
||||
return open.size();
|
||||
}
|
||||
|
||||
/** Erfuellte Wuensche zustellen (beim Join oder sofort). */
|
||||
public void deliver(Player p) {
|
||||
boolean changed = false;
|
||||
List<Wish> done = new ArrayList<>();
|
||||
for (Wish w : wishes.values()) if (w.done && !w.notified && w.owner.equals(p.getUniqueId())) done.add(w);
|
||||
for (Wish w : done) {
|
||||
if (w.gift != null) RewardUtil.give(p, w.gift);
|
||||
p.sendMessage(mgr.msg(w.gift != null ? "wish-fulfilled-gift" : "wish-fulfilled", "%wish%", w.text));
|
||||
wishes.remove(w.id);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) save();
|
||||
}
|
||||
|
||||
// ---------- Admin-Menue ----------
|
||||
|
||||
private List<Wish> allOpen() {
|
||||
List<Wish> list = new ArrayList<>();
|
||||
for (Wish w : wishes.values()) if (!w.done) list.add(w);
|
||||
return list;
|
||||
}
|
||||
|
||||
public void openMenu(Player admin, int page) {
|
||||
List<Wish> open = allOpen();
|
||||
int pages = Math.max(1, (open.size() + PAGE_SIZE - 1) / PAGE_SIZE);
|
||||
page = Math.max(0, Math.min(page, pages - 1));
|
||||
|
||||
Menu holder = new Menu();
|
||||
holder.page = page;
|
||||
holder.inv = Bukkit.createInventory(holder, 54, mgr.raw("wish-menu-title") + " (" + (page + 1) + "/" + pages + ")");
|
||||
SimpleDateFormat fmt = new SimpleDateFormat("dd.MM. HH:mm");
|
||||
for (int i = 0; i < PAGE_SIZE; i++) {
|
||||
int idx = page * PAGE_SIZE + i;
|
||||
if (idx >= open.size()) break;
|
||||
Wish w = open.get(idx);
|
||||
ItemStack it = new ItemStack(Material.PAPER);
|
||||
ItemMeta meta = it.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName(mgr.raw("wish-menu-item-name").replace("%player%", w.name));
|
||||
List<String> lore = new ArrayList<>();
|
||||
for (String line : wrap(w.text, 34)) lore.add("§f" + line);
|
||||
lore.add("§8" + fmt.format(new Date(w.created)));
|
||||
lore.add(" ");
|
||||
lore.add(mgr.raw("wish-menu-hint-left"));
|
||||
lore.add(mgr.raw("wish-menu-hint-shift"));
|
||||
lore.add(mgr.raw("wish-menu-hint-right"));
|
||||
meta.setLore(lore);
|
||||
it.setItemMeta(meta);
|
||||
}
|
||||
holder.inv.setItem(i, it);
|
||||
holder.slotToId.put(i, w.id);
|
||||
}
|
||||
if (page > 0) holder.inv.setItem(45, named(Material.ARROW, mgr.raw("wish-page-prev")));
|
||||
if (page < pages - 1) holder.inv.setItem(53, named(Material.ARROW, mgr.raw("wish-page-next")));
|
||||
if (open.isEmpty()) holder.inv.setItem(22, named(Material.BARRIER, mgr.raw("wish-menu-empty")));
|
||||
admin.openInventory(holder.inv);
|
||||
}
|
||||
|
||||
private static ItemStack named(Material m, String name) {
|
||||
ItemStack it = new ItemStack(m);
|
||||
ItemMeta meta = it.getItemMeta();
|
||||
if (meta != null) { meta.setDisplayName(name); it.setItemMeta(meta); }
|
||||
return it;
|
||||
}
|
||||
|
||||
private static List<String> wrap(String text, int width) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
StringBuilder cur = new StringBuilder();
|
||||
for (String word : text.split(" ")) {
|
||||
if (cur.length() + word.length() + 1 > width && cur.length() > 0) {
|
||||
lines.add(cur.toString());
|
||||
cur = new StringBuilder();
|
||||
}
|
||||
if (cur.length() > 0) cur.append(' ');
|
||||
cur.append(word);
|
||||
}
|
||||
if (cur.length() > 0) lines.add(cur.toString());
|
||||
return lines;
|
||||
}
|
||||
|
||||
public void click(Player admin, Menu menu, int slot, boolean right, boolean shift) {
|
||||
if (slot == 45 && menu.page > 0) { openMenu(admin, menu.page - 1); return; }
|
||||
if (slot == 53) { openMenu(admin, menu.page + 1); return; }
|
||||
Integer id = menu.slotToId.get(slot);
|
||||
if (id == null) return;
|
||||
Wish w = wishes.get(id);
|
||||
if (w == null || w.done) { openMenu(admin, menu.page); return; }
|
||||
|
||||
if (right) {
|
||||
wishes.remove(id);
|
||||
save();
|
||||
admin.sendMessage(mgr.msg("wish-admin-deleted", "%player%", w.name));
|
||||
openMenu(admin, menu.page);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack hand = admin.getInventory().getItemInMainHand();
|
||||
boolean hasItem = hand != null && !hand.getType().isAir();
|
||||
if (!hasItem && !shift) {
|
||||
// Kein Item in der Hand: nicht stillschweigend ohne Geschenk erfuellen
|
||||
admin.sendMessage(mgr.msg("wish-admin-hold"));
|
||||
return;
|
||||
}
|
||||
|
||||
String itemText = "";
|
||||
if (hasItem && !shift) {
|
||||
w.gift = hand.clone();
|
||||
itemText = hand.getAmount() + "x " + hand.getType().name();
|
||||
admin.getInventory().setItemInMainHand(null);
|
||||
}
|
||||
w.done = true;
|
||||
w.notified = false;
|
||||
save();
|
||||
|
||||
Player owner = Bukkit.getPlayer(w.owner);
|
||||
if (owner != null) {
|
||||
deliver(owner);
|
||||
admin.sendMessage(w.gift != null
|
||||
? mgr.msg("wish-admin-done-gift", "%player%", w.name, "%item%", itemText)
|
||||
: mgr.msg("wish-admin-done", "%player%", w.name));
|
||||
} else {
|
||||
admin.sendMessage(mgr.msg("wish-admin-done-offline", "%player%", w.name));
|
||||
}
|
||||
openMenu(admin, menu.page);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldguard.WorldGuard;
|
||||
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Nur laden, wenn WorldGuard installiert ist (sonst NoClassDefFoundError). */
|
||||
final class WorldGuardHook {
|
||||
private WorldGuardHook() {}
|
||||
|
||||
static boolean inAnyRegion(Location loc, List<String> names) {
|
||||
var set = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery()
|
||||
.getApplicableRegions(BukkitAdapter.adapt(loc));
|
||||
for (ProtectedRegion r : set.getRegions()) {
|
||||
for (String n : names) if (r.getId().equalsIgnoreCase(n)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.type.Candle;
|
||||
import org.bukkit.block.data.type.Leaves;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Adventskranz: Vier Kerzen, die an den vier Adventssonntagen nacheinander angehen. */
|
||||
public final class WreathManager {
|
||||
private record Wreath(String world, int x, int y, int z) {}
|
||||
|
||||
// Kerzen: 1., 2., 4. Advent violett, 3. Advent rosa
|
||||
private static final int[][] CANDLE_OFFSETS = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};
|
||||
private static final Material[] CANDLES = {Material.PURPLE_CANDLE, Material.PURPLE_CANDLE,
|
||||
Material.PINK_CANDLE, Material.PURPLE_CANDLE};
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private final File file;
|
||||
private final List<Wreath> wreaths = new ArrayList<>();
|
||||
private final Set<String> blocks = new HashSet<>();
|
||||
private String lastAnnounced = "";
|
||||
private BukkitTask task;
|
||||
|
||||
WreathManager(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.file = new File(mgr.plugin().getDataFolder(), "wreaths.yml");
|
||||
}
|
||||
|
||||
// ---------- Adventsberechnung ----------
|
||||
|
||||
/** Aktueller Advent 0-4 (0 = vor dem 1. Advent oder nach dem Brenn-Ende). */
|
||||
public static int adventNumber(LocalDate today, ChristmasManager mgr) {
|
||||
LocalDate fourth = LocalDate.of(today.getYear(), 12, 24);
|
||||
while (fourth.getDayOfWeek() != DayOfWeek.SUNDAY) fourth = fourth.minusDays(1);
|
||||
LocalDate burnUntil = LocalDate.of(today.getYear(), 12, 26);
|
||||
String until = mgr.cfg().getString("wreath.burn-until", "12-26");
|
||||
try {
|
||||
String[] p = until.split("-");
|
||||
burnUntil = LocalDate.of(today.getYear(), Integer.parseInt(p[0].trim()), Integer.parseInt(p[1].trim()));
|
||||
} catch (Exception ignored) { }
|
||||
if (today.isAfter(burnUntil)) return 0;
|
||||
int n = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!today.isBefore(fourth.minusWeeks(3 - i))) n = i + 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private int currentAdvent() {
|
||||
return adventNumber(LocalDate.now(mgr.plugin().getZone()), mgr);
|
||||
}
|
||||
|
||||
// ---------- Verwaltung ----------
|
||||
|
||||
private static String key(Block b) {
|
||||
return b.getWorld().getName() + ";" + b.getX() + ";" + b.getY() + ";" + b.getZ();
|
||||
}
|
||||
|
||||
public boolean isProtected(Block b) { return blocks.contains(key(b)); }
|
||||
|
||||
void load() {
|
||||
wreaths.clear();
|
||||
blocks.clear();
|
||||
if (file.exists()) {
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(file);
|
||||
lastAnnounced = y.getString("announced", "");
|
||||
ConfigurationSection s = y.getConfigurationSection("wreaths");
|
||||
if (s != null) {
|
||||
for (String k : s.getKeys(false)) {
|
||||
ConfigurationSection w = s.getConfigurationSection(k);
|
||||
if (w == null) continue;
|
||||
Wreath wr = new Wreath(w.getString("world", "world"), w.getInt("x"), w.getInt("y"), w.getInt("z"));
|
||||
wreaths.add(wr);
|
||||
index(wr);
|
||||
}
|
||||
}
|
||||
}
|
||||
stop();
|
||||
task = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::tick, 100L, 20L * 30);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
}
|
||||
|
||||
private void save() {
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
y.set("announced", lastAnnounced);
|
||||
int i = 0;
|
||||
for (Wreath w : wreaths) {
|
||||
y.set("wreaths." + i + ".world", w.world());
|
||||
y.set("wreaths." + i + ".x", w.x());
|
||||
y.set("wreaths." + i + ".y", w.y());
|
||||
y.set("wreaths." + i + ".z", w.z());
|
||||
i++;
|
||||
}
|
||||
try {
|
||||
y.save(file);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("wreaths.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void index(Wreath w) {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
blocks.add(w.world() + ";" + (w.x() + dx) + ";" + w.y() + ";" + (w.z() + dz));
|
||||
blocks.add(w.world() + ";" + (w.x() + dx) + ";" + (w.y() + 1) + ";" + (w.z() + dz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Baut einen Kranz auf dem Block unter dem Ziel; Mitte des Kranzes = ground.getRelative(UP). */
|
||||
public boolean create(Block ground) {
|
||||
Block center = ground.getRelative(0, 1, 0);
|
||||
World w = center.getWorld();
|
||||
Leaves leaves = (Leaves) Material.SPRUCE_LEAVES.createBlockData();
|
||||
leaves.setPersistent(true);
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
if (dx == 0 && dz == 0) continue;
|
||||
Block b = w.getBlockAt(center.getX() + dx, center.getY(), center.getZ() + dz);
|
||||
b.setBlockData(leaves, false);
|
||||
}
|
||||
}
|
||||
// Mitte: Moosteppich-aehnlich als Dekoration
|
||||
w.getBlockAt(center.getX(), center.getY(), center.getZ()).setType(Material.RED_CARPET, false);
|
||||
Wreath wr = new Wreath(w.getName(), center.getX(), center.getY(), center.getZ());
|
||||
wreaths.add(wr);
|
||||
index(wr);
|
||||
save();
|
||||
applyCandles(wr, currentAdvent());
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeNear(Block b) {
|
||||
for (Wreath w : new ArrayList<>(wreaths)) {
|
||||
if (!w.world().equals(b.getWorld().getName())) continue;
|
||||
if (Math.abs(w.x() - b.getX()) <= 2 && Math.abs(w.z() - b.getZ()) <= 2 && Math.abs(w.y() - b.getY()) <= 3) {
|
||||
World world = b.getWorld();
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
for (int dy = 0; dy <= 1; dy++) {
|
||||
blocks.remove(w.world() + ";" + (w.x() + dx) + ";" + (w.y() + dy) + ";" + (w.z() + dz));
|
||||
world.getBlockAt(w.x() + dx, w.y() + dy, w.z() + dz).setType(Material.AIR, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
wreaths.remove(w);
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------- Kerzen ----------
|
||||
|
||||
private void applyCandles(Wreath w, int advent) {
|
||||
World world = Bukkit.getWorld(w.world());
|
||||
if (world == null || !world.isChunkLoaded(w.x() >> 4, w.z() >> 4)) return;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Block b = world.getBlockAt(w.x() + CANDLE_OFFSETS[i][0], w.y() + 1, w.z() + CANDLE_OFFSETS[i][1]);
|
||||
Candle data = (Candle) CANDLES[i].createBlockData();
|
||||
data.setLit(i < advent);
|
||||
data.setCandles(1);
|
||||
b.setBlockData(data, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
int advent = currentAdvent();
|
||||
for (Wreath w : wreaths) applyCandles(w, advent);
|
||||
|
||||
if (advent > 0 && !wreaths.isEmpty() && mgr.cfg().getBoolean("wreath.announce", true)) {
|
||||
String id = LocalDate.now(mgr.plugin().getZone()).getYear() + "-" + advent;
|
||||
if (!id.equals(lastAnnounced)) {
|
||||
lastAnnounced = id;
|
||||
save();
|
||||
Bukkit.broadcastMessage(mgr.msg("wreath-advent", "%n%", String.valueOf(advent)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
/**
|
||||
* PlaceholderAPI-Platzhalter mit dem Praefix xmas. Nur laden, wenn PlaceholderAPI installiert ist.
|
||||
* <pre>
|
||||
* %xmas_days_left% Tage bis Heiligabend
|
||||
* %xmas_countdown% Text des Countdowns
|
||||
* %xmas_advent% aktueller Advent (0-4)
|
||||
* %xmas_trees% Magic Trees des Spielers
|
||||
* %xmas_snowball_points% Punkte im Schneeballspiel
|
||||
* %xmas_snowball_rank% Platz im Schneeballspiel
|
||||
* %xmas_streak% aktuelle Login-Serie
|
||||
* %xmas_streak_best% beste Login-Serie
|
||||
* %xmas_wichteln% Name des Wichtel-Partners (falls gezogen)
|
||||
* </pre>
|
||||
*/
|
||||
final class XmasExpansion extends PlaceholderExpansion {
|
||||
private final ChristmasManager mgr;
|
||||
|
||||
XmasExpansion(ChristmasManager mgr) { this.mgr = mgr; }
|
||||
|
||||
@Override public String getIdentifier() { return "xmas"; }
|
||||
@Override public String getAuthor() { return "M_Viper"; }
|
||||
@Override public String getVersion() { return mgr.plugin().getDescription().getVersion(); }
|
||||
@Override public boolean persist() { return true; }
|
||||
|
||||
@Override
|
||||
public String onRequest(OfflinePlayer p, String params) {
|
||||
switch (params.toLowerCase()) {
|
||||
case "days_left": return String.valueOf(mgr.countdown().daysLeft());
|
||||
case "countdown": return org.bukkit.ChatColor.stripColor(mgr.countdown().text());
|
||||
case "advent": return String.valueOf(WreathManager.adventNumber(
|
||||
java.time.LocalDate.now(mgr.plugin().getZone()), mgr));
|
||||
default: break;
|
||||
}
|
||||
if (p == null) return "";
|
||||
switch (params.toLowerCase()) {
|
||||
case "trees": return String.valueOf(mgr.trees().countOf(p.getUniqueId()));
|
||||
case "snowball_points": return String.valueOf(mgr.snowball().points(p.getUniqueId()));
|
||||
case "snowball_rank": return String.valueOf(mgr.snowball().rank(p.getUniqueId()));
|
||||
case "streak": return String.valueOf(mgr.streaks().streak(p.getUniqueId()));
|
||||
case "streak_best": return String.valueOf(mgr.streaks().best(p.getUniqueId()));
|
||||
case "wichteln": return mgr.wichteln().targetName(p.getUniqueId());
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
# =============================================================================
|
||||
# Texte stehen in christmas_messages_de.yml / christmas_messages_en.yml
|
||||
# (Sprache folgt general.language aus der config.yml).
|
||||
#
|
||||
# Weihnachts-Module (ersetzen Christmas+ und das X-Mas "Magic Tree" Plugin)
|
||||
# Befehle: /christmas (alias /xmas) und /cracker
|
||||
# Items: "MATERIAL:Anzahl", z.B. "DIAMOND:2"
|
||||
# =============================================================================
|
||||
|
||||
crystal:
|
||||
material: NETHER_STAR
|
||||
name: "&bWeihnachts-Kristall"
|
||||
lore:
|
||||
- "&7Konzentrierter Weihnachtsgeist"
|
||||
- "&7Rechtsklick auf einen Setzling,"
|
||||
- "&7um ihn mit Magie zu füllen!"
|
||||
|
||||
# Befehl, mit dem Permissions als Belohnung vergeben werden (Kalender, Cracker).
|
||||
# Beispiel LuckPerms: "lp user %player% permission set %permission% true"
|
||||
permission-command: ""
|
||||
|
||||
# --- Schnee ---------------------------------------------------------------
|
||||
snow:
|
||||
enable: true
|
||||
# Stärke 1-5
|
||||
level: 3
|
||||
# Ticks zwischen zwei Partikel-Wellen (höher = weniger Last)
|
||||
display-task-interval: 2
|
||||
default-on: true
|
||||
disable-in-worlds: []
|
||||
# Nur in diesen WorldGuard-Regionen (nur wenn WorldGuard installiert ist). Leer = überall.
|
||||
enable-only-in-regions: false
|
||||
regions: []
|
||||
|
||||
# --- Schneemänner --------------------------------------------------------
|
||||
snowman:
|
||||
# Deko-Schneemänner (/xmas snowman spawn [stand|walk] [Name]): stehend oder herumlaufend, unverwundbar
|
||||
deco:
|
||||
particles: true
|
||||
# Kürbis-Kopf behalten
|
||||
hat: true
|
||||
# Standard beim Aufstellen ohne Angabe: true = laufend, false = stehend
|
||||
# (/xmas snowman spawn stand|walk [Name] überschreibt das pro Schneemann)
|
||||
walk: false
|
||||
# Umkreis in Blöcken
|
||||
walk-radius: 8
|
||||
# Schnee-Spur der laufenden Schneemänner: fade = verschwindet wieder, none = keine Spur, keep = bleibt liegen
|
||||
trail: fade
|
||||
trail-fade-seconds: 8
|
||||
# Normal gebaute Schneemänner (Schneeblöcke + Kürbis) geben ab und zu ein Geschenk
|
||||
player:
|
||||
enable: true
|
||||
max-per-player: 3
|
||||
name: "&f%player%s Schneemann"
|
||||
# Sekunden zwischen zwei Geschenken
|
||||
gift-interval: 300
|
||||
gifts:
|
||||
- "SNOWBALL:8"
|
||||
- "COOKIE:3"
|
||||
- "COAL:1"
|
||||
- "GOLD_NUGGET:2"
|
||||
|
||||
# --- Santa-Event ----------------------------------------------------------
|
||||
santa:
|
||||
# Weihnachtsmelodie mit Glocken, wenn Santa kommt
|
||||
melody:
|
||||
enable: true
|
||||
volume: 1.0
|
||||
# Ticks pro Achtelnote (kleiner = schneller)
|
||||
tempo: 3
|
||||
# false = nur Spieler in Santas Welt, true = alle Spieler
|
||||
everyone: false
|
||||
duration: 300
|
||||
drop-interval: 3
|
||||
# Chat-Nachricht "Santa hat ein Geschenk fallen lassen" höchstens alle X Sekunden (0 = nie)
|
||||
drop-message-interval: 60
|
||||
# Chance (in %) pro Drop-Intervall, dass wirklich ein Geschenk fällt
|
||||
drop-chance: 100
|
||||
radius: 6
|
||||
name: "&c&lSanta Claus"
|
||||
# AUTO = Spielermodell (Mannequin, ab Minecraft 1.21.9) wenn möglich, sonst Rüstungsständer.
|
||||
# ARMOR_STAND erzwingt den Rüstungsständer.
|
||||
model: AUTO
|
||||
# Skin: Textur-URL (textures.minecraft.net) ODER Spielername. Leer = Standard-Skin mit roter Lederrüstung.
|
||||
skin-url: "https://textures.minecraft.net/texture/b2aad465030fedcd6e5a255ac228c357f731343bdf159ea78a61f4146b27289d"
|
||||
skin-player: ""
|
||||
# Santa läuft in diesem Radius herum (0 = steht still)
|
||||
walk-radius: 8
|
||||
# Lauftempo in Blöcken pro Tick (0.07 = gemütlicher Spaziergang)
|
||||
walk-speed: 0.07
|
||||
# Anflug mit Schlitten und zwei Rentieren, bevor Santa landet
|
||||
sleigh:
|
||||
enable: true
|
||||
flight-seconds: 15
|
||||
distance: 70
|
||||
height: 30
|
||||
reindeer-names: ["Rudolph", "Blitzen"]
|
||||
snow-trail:
|
||||
enabled: true
|
||||
fade-seconds: 5
|
||||
gifts:
|
||||
- "DIAMOND:1"
|
||||
- "EMERALD:2"
|
||||
- "COOKIE:8"
|
||||
- "CAKE:1"
|
||||
- "GOLD_INGOT:3"
|
||||
- "COAL:5"
|
||||
# Cracker als Santa-Geschenk: "cracker:<id>:<Anzahl>" als Eintrag in gifts, z.B. "cracker:cracker:1"
|
||||
locations: []
|
||||
# - world: world
|
||||
# x: 0.5
|
||||
# y: 80
|
||||
# z: 0.5
|
||||
|
||||
# --- Cracker --------------------------------------------------------------
|
||||
crackers:
|
||||
cracker:
|
||||
material: FIREWORK_STAR
|
||||
name: "&c&lWeihnachts-Cracker"
|
||||
lore:
|
||||
- "&7Rechtsklick zum Öffnen!"
|
||||
rewards:
|
||||
- { chance: 40, item: "COOKIE:8" }
|
||||
- { chance: 30, item: "GOLD_INGOT:3" }
|
||||
- { chance: 20, item: "DIAMOND:1" }
|
||||
- { chance: 10, item: "EMERALD:5" }
|
||||
# Beispiel Befehl: - { chance: 5, command: "give %player% diamond 3" }
|
||||
|
||||
# --- Magic Tree -----------------------------------------------------------
|
||||
magic-tree:
|
||||
enable: true
|
||||
# Max. Bäume pro Spieler
|
||||
tree-limit: 3
|
||||
# Das Event endet automatisch an diesem Datum (Format dd-MM-yyyy HH-mm-ss)
|
||||
holiday-ends:
|
||||
enabled: true
|
||||
date: "10-01-2027 00-00-00"
|
||||
# Beim Abbau nach Eventende alle ausgegebenen Ressourcen zurückgeben
|
||||
resource-back: true
|
||||
# Chance, dass ein Geschenk Inhalt hat
|
||||
luck:
|
||||
enabled: false
|
||||
chance: 75
|
||||
# Weihnachts-Kristall in der Werkbank herstellbar:
|
||||
# Diamanten oben/links/rechts/unten, Smaragd in der Mitte
|
||||
crystal-recipe: true
|
||||
# Partikel-Show am Baum (Spirale, Lichter, Stern, Glitzer über Geschenken)
|
||||
particles:
|
||||
enable: true
|
||||
# 0.2 (dezent) bis 5.0 (sehr viel). Standard 2.0
|
||||
intensity: 2.0
|
||||
max-presents-per-tree: 5
|
||||
# Geschenk-Köpfe (Texturen-URLs von textures.minecraft.net)
|
||||
presents:
|
||||
- "http://textures.minecraft.net/texture/21bc9d42b0041e8f95cb9b26628fdaf50cd0e36f7bb9d6b3a4d2af3949da97d6"
|
||||
- "http://textures.minecraft.net/texture/2b1ec7dc753061ca174424ea45cf9490b39cd5dcca477d138a603e6be755ec72"
|
||||
# Weitere Geschenke per /xmas addhand (gespeichert in gifts.yml)
|
||||
gifts:
|
||||
- DIAMOND
|
||||
- EMERALD
|
||||
- IRON_SWORD
|
||||
- IRON_HELMET
|
||||
- IRON_CHESTPLATE
|
||||
- IRON_BOOTS
|
||||
- CAKE
|
||||
- COOKIE
|
||||
- BLAZE_ROD
|
||||
- DIAMOND_SWORD
|
||||
- IRON_INGOT
|
||||
- GOLD_INGOT
|
||||
- SPONGE
|
||||
- DIAMOND_HOE
|
||||
- NAME_TAG
|
||||
# gift-cooldown in Sekunden (-1 = keine Geschenke), lvlup = Items, die zum nächsten Level nötig sind
|
||||
levels:
|
||||
sapling:
|
||||
gift-cooldown: -1
|
||||
lvlup:
|
||||
DIAMOND: 1
|
||||
REDSTONE: 10
|
||||
ENDER_PEARL: 1
|
||||
small_tree:
|
||||
gift-cooldown: 300
|
||||
lvlup:
|
||||
DIAMOND: 3
|
||||
GOLD_INGOT: 5
|
||||
BLAZE_POWDER: 10
|
||||
SNOWBALL: 30
|
||||
tree:
|
||||
gift-cooldown: 180
|
||||
lvlup:
|
||||
DIAMOND: 5
|
||||
EMERALD: 3
|
||||
GOLD_NUGGET: 8
|
||||
GLOWSTONE_DUST: 16
|
||||
magic_tree:
|
||||
gift-cooldown: 120
|
||||
lvlup: {}
|
||||
|
||||
# --- Kalender-Zusatz (Christmas+ Funktionen) -------------------------------
|
||||
# Wirken auf den Adventskalender (config.yml -> rewards.<Tag>)
|
||||
calendar-extras:
|
||||
# Beim Abholen eines Tages allen Spielern eine Meldung zeigen
|
||||
broadcast-claim: false
|
||||
# true: Nur der heutige Tag ist öffnbar; vergangene Tage nur mit Recht christmasplus.adventcalendar.claimpast
|
||||
restrict-past-days: false
|
||||
|
||||
# --- Countdown bis Heiligabend ------------------------------------------------
|
||||
countdown:
|
||||
enable: true
|
||||
# Zieldatum Monat-Tag
|
||||
target: "12-24"
|
||||
# Ab wie vielen Tagen vorher wird der Countdown angezeigt
|
||||
show-days-before: 60
|
||||
bossbar: true
|
||||
join-message: true
|
||||
|
||||
# --- Plätzchen und Lebkuchen --------------------------------------------------
|
||||
# Verteilen mit /xmas cookie give <Spieler> <id> oder als Item "cookie:<id>:<Anzahl>"
|
||||
# in Belohnungen, Crackern, Santa-Geschenken usw.
|
||||
# effects: "EFFEKT:Sekunden:Stufe" (Stufe 0 = I)
|
||||
cookies:
|
||||
zimtstern:
|
||||
material: COOKIE
|
||||
name: "&6Zimtstern"
|
||||
lore:
|
||||
- "&7Knusprig und zimtig."
|
||||
- "&7Gibt kurz Tempo."
|
||||
effects: ["speed:30:1"]
|
||||
lebkuchen:
|
||||
material: BREAD
|
||||
name: "&cLebkuchen"
|
||||
lore:
|
||||
- "&7Frisch aus dem Ofen."
|
||||
- "&7Regeneriert Leben."
|
||||
effects: ["regeneration:10:1"]
|
||||
bratapfel:
|
||||
material: APPLE
|
||||
name: "&eBratapfel"
|
||||
lore:
|
||||
- "&7Warm und süß."
|
||||
- "&7Gibt Absorption."
|
||||
effects: ["absorption:60:1"]
|
||||
|
||||
# --- Wichteln ----------------------------------------------------------------
|
||||
wichteln:
|
||||
# Beim Verteilen den Schenkenden verraten?
|
||||
reveal-giver: false
|
||||
|
||||
# --- Adventskranz ------------------------------------------------------------
|
||||
wreath:
|
||||
# Meldung an alle am Adventssonntag
|
||||
announce: true
|
||||
# Bis zu diesem Tag (Monat-Tag) brennen die Kerzen
|
||||
burn-until: "12-26"
|
||||
|
||||
# --- Schneeball-Minispiel ----------------------------------------------------
|
||||
snowball:
|
||||
duration: 180
|
||||
points-per-hit: 1
|
||||
# So viele Schneebälle haben Spieler in der Arena immer mindestens
|
||||
refill-amount: 16
|
||||
# Belohnungen für Platz 1 bis 3 (item / command / permission, %player% wird ersetzt)
|
||||
rewards:
|
||||
1:
|
||||
- { item: "DIAMOND:3" }
|
||||
2:
|
||||
- { item: "EMERALD:5" }
|
||||
3:
|
||||
- { item: "GOLD_INGOT:5" }
|
||||
|
||||
# --- Login-Serie -------------------------------------------------------------
|
||||
login-streak:
|
||||
enable: true
|
||||
# Zeitraum (Monat-Tag), in dem die Serie zählt
|
||||
from: "12-01"
|
||||
until: "12-31"
|
||||
# Nach so vielen Tagen beginnen die Belohnungen von vorn
|
||||
cycle: 7
|
||||
rewards:
|
||||
1:
|
||||
- { item: "COOKIE:5" }
|
||||
2:
|
||||
- { item: "cookie:zimtstern:2" }
|
||||
3:
|
||||
- { item: "GOLD_INGOT:3" }
|
||||
4:
|
||||
- { item: "cookie:lebkuchen:2" }
|
||||
5:
|
||||
- { item: "EMERALD:2" }
|
||||
6:
|
||||
- { item: "cracker:cracker:1" }
|
||||
7:
|
||||
- { item: "DIAMOND:2" }
|
||||
|
||||
# --- Weihnachts- und Neujahrsgruß ---------------------------------------------
|
||||
# Einmal pro Tag Chat-Gruß an alle (und beim Join), Schlüssel verweisen auf die Sprachdatei.
|
||||
greetings:
|
||||
enable: true
|
||||
join-fireworks: true
|
||||
fireworks-on-broadcast: true
|
||||
days:
|
||||
"12-24": greeting-christmas-eve
|
||||
"12-25": plugin-enabled
|
||||
"12-31": greeting-new-years-eve
|
||||
"01-01": final-wish
|
||||
|
||||
# --- Kamin-Geschenke (Socke aufhängen) -----------------------------------------
|
||||
stocking:
|
||||
enable: true
|
||||
# Socke aufhängen ab / Geschenk ab / Abholen möglich bis (Monat-Tag)
|
||||
hang-from: "12-01"
|
||||
open-on: "12-25"
|
||||
until: "12-31"
|
||||
# Wie viele Socken ein Spieler per /xmas stocking place aufstellen darf
|
||||
max-placed-per-player: 3
|
||||
rewards:
|
||||
- { item: "COOKIE:6" }
|
||||
- { item: "cookie:zimtstern:2" }
|
||||
- { item: "GOLD_INGOT:2" }
|
||||
- { item: "present:1" }
|
||||
|
||||
# --- Santas Socken-Flug ---------------------------------------------------------
|
||||
# Am Weihnachtstag (und dem Tag danach) fliegt Santa von aufgestellter Socke zu aufgestellter
|
||||
# Socke (/xmas stocking place) und legt unter jeder ein Geschenk ab.
|
||||
stocking-flight:
|
||||
enable: true
|
||||
# Zeitraum (Monat-Tag, einschließlich): Weihnachtstag und ein Tag danach
|
||||
from: "12-24"
|
||||
until: "12-25"
|
||||
# Mindestabstand zwischen zwei Flügen in Minuten
|
||||
interval-minutes: 20
|
||||
# Wie viele Socken pro Flug beliefert werden
|
||||
max-stockings-per-flight: 12
|
||||
# Flugtempo in Blöcken pro Tick
|
||||
speed: 0.7
|
||||
# Nur der Besitzer der Socke darf das Geschenk öffnen
|
||||
owner-only: true
|
||||
# Inhalt des Geschenks (leer = die Belohnungen von stocking.rewards)
|
||||
rewards: []
|
||||
|
||||
# --- Nikolausmütze ------------------------------------------------------------
|
||||
santa-hat:
|
||||
enable: true
|
||||
from: "12-01"
|
||||
until: "12-31"
|
||||
# Beim Join anklickbares Angebot im Chat
|
||||
offer-on-join: true
|
||||
# Statt Angebot direkt aufsetzen
|
||||
auto-equip: false
|
||||
|
||||
# --- Weihnachtsquiz ------------------------------------------------------------
|
||||
quiz:
|
||||
answer-seconds: 30
|
||||
# Automatisch alle X Minuten eine Frage (nur wenn genug Spieler online sind)
|
||||
auto:
|
||||
enable: false
|
||||
interval-minutes: 20
|
||||
min-players: 2
|
||||
default-reward:
|
||||
- { item: "EMERALD:2" }
|
||||
# a = erlaubte Antworten (Groß-/Kleinschreibung und Sonderzeichen egal), reward optional
|
||||
questions:
|
||||
- { q: "Wie viele Kerzen hat ein Adventskranz?", a: ["4", "vier"] }
|
||||
- { q: "Welches Tier zieht den Schlitten des Weihnachtsmanns?", a: ["Rentier", "Rentiere"] }
|
||||
- { q: "Wie heißt das Rentier mit der roten Nase?", a: ["Rudolph", "Rudolf"] }
|
||||
- { q: "Aus wie vielen Türchen besteht ein klassischer Adventskalender?", a: ["24", "vierundzwanzig"] }
|
||||
- { q: "In welchem Monat ist Weihnachten?", a: ["Dezember"] }
|
||||
- { q: "Welcher Baum wird traditionell zu Weihnachten geschmückt?", a: ["Tannenbaum", "Tanne", "Weihnachtsbaum", "Fichte"] }
|
||||
- { q: "Wie heißt der Weihnachtsmann auf Englisch?", a: ["Santa Claus", "Santa"] }
|
||||
- { q: "Wie viele Adventssonntage gibt es?", a: ["4", "vier"] }
|
||||
- { q: "Wie viele Schneeblöcke braucht man für einen Schneegolem?", a: ["2", "zwei"] }
|
||||
- { q: "Welcher Mob wirft Schneebälle auf Monster?", a: ["Schneegolem", "Schneemann", "Snow Golem"] }
|
||||
|
||||
# --- Wunschzettel --------------------------------------------------------------
|
||||
wishes:
|
||||
max-open-per-player: 3
|
||||
max-length: 120
|
||||
|
||||
# --- Weihnachtsmarkt-Stände ---------------------------------------------------
|
||||
# Aufstellen mit /xmas market spawn <id>. give: wie bei allen Items, auch cookie:/cracker:/crystal:/present:.
|
||||
# price: bis zu 2 Items. Nach /xmas reload werden die Angebote aktualisiert.
|
||||
market:
|
||||
stalls:
|
||||
plaetzchen:
|
||||
name: "&6Plätzchen-Stand"
|
||||
profession: BUTCHER
|
||||
trades:
|
||||
- { give: "cookie:zimtstern:2", price: ["EMERALD:1"] }
|
||||
- { give: "cookie:lebkuchen:2", price: ["EMERALD:1"] }
|
||||
- { give: "cookie:bratapfel:1", price: ["GOLD_INGOT:2"] }
|
||||
- { give: "COOKIE:8", price: ["WHEAT:8"] }
|
||||
deko:
|
||||
name: "&2Deko-Stand"
|
||||
profession: LIBRARIAN
|
||||
trades:
|
||||
- { give: "present:1", price: ["EMERALD:3"] }
|
||||
- { give: "SPRUCE_SAPLING:4", price: ["GOLD_INGOT:1"] }
|
||||
- { give: "RED_CANDLE:4", price: ["IRON_INGOT:2"] }
|
||||
- { give: "LANTERN:2", price: ["IRON_INGOT:3"] }
|
||||
- { give: "SNOWBALL:16", price: ["COAL:1"] }
|
||||
- { give: "crystal:1", price: ["DIAMOND:5", "EMERALD:5"] }
|
||||
|
||||
# --- Portale zum Weihnachtsmarkt -----------------------------------------------
|
||||
# Portale funktionieren NUR, wenn ein Admin/OP den Marktplatz mit /xmas market set festgelegt hat
|
||||
# (aufheben mit /xmas market unset). Spieler bauen sich mit /xmas portal create (auf den Boden
|
||||
# schauen) ein Portal aus Schneebloecken, das dorthin fuehrt.
|
||||
portal:
|
||||
enable: true
|
||||
# Kosten fuer ein Portal: Das Portal besteht aus 10 Schneebloecken (Admins bauen kostenlos)
|
||||
cost: ["SNOW_BLOCK:10"]
|
||||
# Beim Entfernen durch den Besitzer die Kosten zurueckgeben
|
||||
refund: true
|
||||
limit-per-player: 1
|
||||
# Wie lange man im Portal stehen muss
|
||||
teleport-delay-seconds: 3
|
||||
cooldown-seconds: 10
|
||||
# Leer = ueberall erlaubt, sonst nur in diesen Welten
|
||||
allowed-worlds: []
|
||||
@@ -0,0 +1,275 @@
|
||||
messages:
|
||||
prefix: "&c[Christmas] &f"
|
||||
no-permission: "&cDazu hast du keine Berechtigung."
|
||||
player-only: "&cDieser Befehl geht nur im Spiel."
|
||||
player-offline: "&cSpieler nicht gefunden."
|
||||
usage: "&cNutzung: /christmas <help|snow|santa|give|gifts|addhand|snowman|countdown|cookie|wichteln|wreath|snowball|deco|stocking|hat|quiz|wish|wishes|market|portal|end|reload>"
|
||||
help:
|
||||
- "&2/xmas &7- Version und Status"
|
||||
- "&2/xmas snow &7- Schnee für dich an/aus"
|
||||
- "&2/xmas countdown &7- Countdown-Leiste an/aus"
|
||||
- "&2/xmas santa start [Zeit] [Welt x y z | random] | stop | teleport"
|
||||
- "&2/xmas give <Name> [Anzahl] &7- Weihnachts-Kristall geben"
|
||||
- "&2/xmas gifts &7- Geschenke unter alle Bäume"
|
||||
- "&2/xmas addhand &7- Item in der Hand als Geschenk hinzufügen"
|
||||
- "&2/xmas snowman spawn [stand|walk] [Name] | remove &7- Deko-Schneemann (stehend/laufend)"
|
||||
- "&2/xmas cookie list | give <Spieler> <id> [Anzahl]"
|
||||
- "&2/xmas wichteln join|leave|who|gift|status &7- Wichteln"
|
||||
- "&2/xmas wichteln open|draw|deliver|reset &7- (Admin)"
|
||||
- "&2/xmas wreath create|remove &7- Adventskranz"
|
||||
- "&2/xmas snowball arena [Radius] | start [Zeit] | stop | top"
|
||||
- "&2/xmas deco [undo] &7- Deko-Menü"
|
||||
- "&2/xmas stocking [place | remove | flight] &7- Socke aufhängen / abholen / an die Wand hängen"
|
||||
- "&2/xmas hat &7- Nikolausmütze"
|
||||
- "&2/xmas wish <Text> | list | clear &7- Wunschzettel"
|
||||
- "&2/xmas wishes &7- Wunschzettel ansehen (Admin)"
|
||||
- "&2/xmas quiz start [Nr] | skip &7- Weihnachtsquiz (Admin)"
|
||||
- "&2/xmas market spawn <id> | remove | list &7- Marktstände (Admin)"
|
||||
- "&2/xmas portal create|remove|info &7- Portal zum Weihnachtsmarkt"
|
||||
- "&2/xmas market set|unset|tp|back &7- Marktpunkt (Admin) / Rückweg"
|
||||
- "&2/xmas end &7- Event beenden"
|
||||
- "&2/xmas reload &7- Konfiguration neu laden"
|
||||
- "&2/cracker list | get [id] [Anzahl] | give <Spieler> [id] [Anzahl]"
|
||||
info: "&aX-Mas Modul aktiv. Status: &e%status% &7- Bäume: &e%trees%"
|
||||
status-active: "AKTIV"
|
||||
status-ended: "BEENDET"
|
||||
reloaded: "&aChristmas-Konfiguration neu geladen."
|
||||
calendar-test-on: "&aKalender-Testmodus &leingeschaltet&a: Du kannst jeden Tag öffnen."
|
||||
calendar-test-off: "&7Kalender-Testmodus ausgeschaltet."
|
||||
past-day-restricted: "&cVergangene Tage kannst du nicht mehr öffnen."
|
||||
plugin-enabled: "&aFrohe Weihnachten und ein gutes neues Jahr!"
|
||||
timeout: "&cDie Feiertage sind vorbei! Bis nächstes Jahr!"
|
||||
final-wish: "&6Alles Gute zum neuen Jahr, meine Freunde!"
|
||||
end-set: "&7Event beendet. Spieler können ihre Bäume abbauen und bekommen Ressourcen zurück."
|
||||
gifts-spawned: "&cHo! &2Ho! &cHo! &3Zeit, die Geschenke zu prüfen!"
|
||||
addhand-ok: "&aItem als Geschenk hinzugefügt."
|
||||
addhand-empty: "&cDu hältst kein Item in der Hand."
|
||||
crystal-given: "&aDu hast &e%amount%x Weihnachts-Kristall &aerhalten."
|
||||
crystal-gave: "&a%player% hat %amount% Kristall(e) bekommen."
|
||||
|
||||
# Schnee
|
||||
snow-on: "&aSchnee ist für dich &leingeschaltet&a."
|
||||
snow-off: "&7Schnee ist für dich &causgeschaltet&7."
|
||||
snow-disabled-global: "&cSchnee ist auf dem Server deaktiviert."
|
||||
|
||||
# Schneemänner
|
||||
snowman-usage: "&cNutzung: /christmas snowman spawn [stand|walk] [Name] | remove"
|
||||
snowman-spawned: "&aDeko-Schneemann aufgestellt."
|
||||
snowman-removed: "&7Deko-Schneemann entfernt."
|
||||
snowman-none: "&cKein Deko-Schneemann in 6 Blöcken Umkreis."
|
||||
snowman-built: "&aDein Schneemann gibt dir ab und zu ein Geschenk! (maximal %max% Schneemänner)"
|
||||
snowman-limit: "&cDu hast schon %max% Schneemänner, dieser gibt keine Geschenke."
|
||||
|
||||
# Santa
|
||||
santa-started: "&aDas Santa-Claus-Event hat begonnen! Santa ist in &e%world% &abei &ex=%x%, y=%y%, z=%z%&a und wartet &e%duration%&a. (/christmas santa teleport)"
|
||||
santa-started-sender: "&aDu hast das Santa-Claus-Event für &e%duration% &agestartet."
|
||||
santa-dropped: "&aSanta hat gerade ein Geschenk fallen lassen!"
|
||||
calendar-claimed-broadcast: "&e%player% &ahat das Geschenk von Kalendertag &e%day% &aabgeholt."
|
||||
calendar-claimed-self: "&aDu hast das Geschenk von Tag &e%day% &aabgeholt."
|
||||
santa-stopped: "&7Der Weihnachtsmann ist wieder weitergezogen."
|
||||
santa-already: "&cDer Weihnachtsmann ist bereits aktiv."
|
||||
santa-not-active: "&cDer Weihnachtsmann ist gerade nicht aktiv."
|
||||
santa-no-locations: "&cEs sind keine Santa-Orte in der christmas.yml hinterlegt."
|
||||
santa-usage: "&cNutzung: /christmas santa start [Sekunden] [Welt x y z | random] | stop | teleport"
|
||||
santa-bossbar: "&cSanta Claus &f- noch &e%time%s"
|
||||
santa-teleported: "&aDu wurdest zu Santa teleportiert."
|
||||
|
||||
# Cracker
|
||||
cracker-given: "&aDu hast &e%amount%x %id% &aerhalten."
|
||||
cracker-gave: "&a%player% hat %amount%x %id% bekommen."
|
||||
cracker-unknown: "&cUnbekannter Cracker: %id%"
|
||||
cracker-opened: "&6Knall! &fDu hast einen Cracker geöffnet."
|
||||
cracker-usage: "&cNutzung: /cracker list | get [id] [Anzahl] | give <Spieler> [id] [Anzahl]"
|
||||
|
||||
# Magic Tree
|
||||
tree:
|
||||
grow-lvl-ready: "&aWusch! Rechtsklick auf den Baum, damit er wächst!"
|
||||
grow-req-list-title: "&eBenötigt (Items neben den Baum werfen):"
|
||||
grow-not-enough-place: "&cHier ist nicht genug Platz zum Wachsen."
|
||||
grow-lvl-max: "&6Dieser Baum hat sein maximales Level erreicht!"
|
||||
grow-fed: "&7Der Baum hat &e%amount%x %item% &7aufgenommen."
|
||||
tree-limit: "&cSei nicht gierig! (Baum-Limit erreicht)"
|
||||
tree-planted: "&aDein Magic Tree wurde gepflanzt! Wirf die benötigten Items neben den Baum."
|
||||
tree-disabled: "&cMagic Trees sind beendet/deaktiviert."
|
||||
destroy-sapling: "&cDieses Wunder kommt nicht wieder. Wirklich abbauen? Nochmal abbauen zum Bestätigen."
|
||||
destroy-leaves-santa: "&cDer Weihnachtsmann sieht alles."
|
||||
destroy-leaves-tut: "&7Um den Baum zu zerstören, fälle den Stamm."
|
||||
destroy-warning: "&cBist du sicher? Dein Fortschritt geht verloren, keine Ressourcen zurück. Nochmal abbauen zum Bestätigen."
|
||||
destroy-fail-owner: "&cDas ist nicht dein Baum!"
|
||||
destroy-complete: "&4Du bist ein MONSTER!"
|
||||
destroy-refund: "&aDer Baum wurde abgebaut, deine Ressourcen kommen zurück."
|
||||
not-owner: "&cDas ist nicht dein Baum."
|
||||
info: "&aMagic Tree von &e%owner%&a, Level: &e%level%"
|
||||
gift:
|
||||
unluck-message: "&7Vielleicht hat der Weihnachtsmann dich auf die schwarze Liste gesetzt. Nächstes Mal klappt es bestimmt!"
|
||||
opened: "&aDu hast ein Geschenk geöffnet!"
|
||||
|
||||
# Countdown
|
||||
countdown-bossbar: "&cNoch &e%days% &cTage bis Heiligabend"
|
||||
countdown-bossbar-one: "&cNoch &e1 &cTag bis Heiligabend"
|
||||
countdown-not-yet: "&7Noch &e%days% &7Tage bis Heiligabend. Die Anzeige startet in &e%in% &7Tag(en)."
|
||||
countdown-today: "&aHeute ist Heiligabend! Frohe Weihnachten!"
|
||||
countdown-on: "&aCountdown-Leiste eingeschaltet."
|
||||
countdown-off: "&7Countdown-Leiste ausgeschaltet."
|
||||
|
||||
# Plätzchen
|
||||
cookie-usage: "&cNutzung: /xmas cookie list | give <Spieler> <id> [Anzahl]"
|
||||
cookie-unknown: "&cUnbekanntes Plätzchen: %id%"
|
||||
cookie-given: "&aDu hast &e%amount%x %id% &aerhalten."
|
||||
|
||||
# Wichteln
|
||||
wi-open: "&aDas Wichteln ist eröffnet! Mit &e/xmas wichteln join &amachst du mit."
|
||||
wi-joined: "&aDu bist beim Wichteln dabei!"
|
||||
wi-already: "&7Du bist schon angemeldet."
|
||||
wi-left: "&7Du bist aus dem Wichteln ausgetreten."
|
||||
wi-not-joined: "&cDu nimmst nicht am Wichteln teil."
|
||||
wi-wrong-state: "&cDas geht gerade nicht (falsche Phase)."
|
||||
wi-too-few: "&cEs braucht mindestens 3 Teilnehmer."
|
||||
wi-drawn: "&aGezogen! Jeder hat einen Wichtel-Partner. Schau mit &e/xmas wichteln who &anach, wen du beschenkst."
|
||||
wi-who: "&aDu beschenkst: &e%name%"
|
||||
wi-gift-empty: "&cNimm das Geschenk in die Hand."
|
||||
wi-gift-saved: "&aDein Geschenk für &e%name% &aist gespeichert. (Mit einem anderen Item in der Hand kannst du es ersetzen.)"
|
||||
wi-delivered: "&6Die Wichtel-Geschenke wurden verteilt! (&e%count% &6Geschenke)"
|
||||
wi-received: "&aDu hast ein Wichtel-Geschenk bekommen!"
|
||||
wi-received-from: "&aDu hast ein Wichtel-Geschenk von &e%from% &abekommen!"
|
||||
wi-reset: "&7Wichteln zurückgesetzt."
|
||||
wi-status: "&aWichteln: &e%state% &7- Teilnehmer: &e%count% &7- Geschenke: &e%gifts%"
|
||||
|
||||
# Adventskranz
|
||||
wreath-created: "&aAdventskranz gebaut."
|
||||
wreath-removed: "&7Adventskranz entfernt."
|
||||
wreath-none: "&cKein Adventskranz in der Nähe."
|
||||
wreath-usage: "&cNutzung: /xmas wreath create | remove (schau auf den Boden)"
|
||||
wreath-advent: "&6Heute ist der &e%n%. Advent&6! Am Adventskranz brennen jetzt &e%n% &6Kerzen."
|
||||
|
||||
# Schneeball-Spiel
|
||||
sb-usage: "&cNutzung: /xmas snowball arena [Radius] | start [Zeit] | stop | top"
|
||||
sb-arena-set: "&aSchneeball-Arena gesetzt (Radius &e%radius%&a)."
|
||||
sb-no-arena: "&cEs ist noch keine Arena gesetzt (/xmas snowball arena)."
|
||||
sb-already: "&cDas Schneeball-Spiel läuft schon."
|
||||
sb-not-active: "&cDas Schneeball-Spiel läuft nicht."
|
||||
sb-started: "&b&lSchneeballschlacht! &7Für &e%time% &7in der Arena (&e%world% %x% %z%&7). Wirf Schneebälle auf andere Spieler!"
|
||||
sb-ended: "&b&lSchneeballschlacht beendet! &7Die Besten:"
|
||||
sb-top-line: "&e%rank%. &f%player% &7- &a%points% Punkte"
|
||||
sb-no-scores: "&7Noch keine Punkte."
|
||||
sb-actionbar: "&fPunkte: &a%points% &7| Platz &e%rank% &7| noch &c%time%s"
|
||||
sb-reward: "&aGlückwunsch zu Platz %rank%! Deine Belohnung wurde ausgegeben."
|
||||
|
||||
# Deko
|
||||
deco-menu-title: "Weihnachts-Deko"
|
||||
deco-click-hint: "&7Klicken, dann wird sie dort aufgestellt, wo du hinschaust."
|
||||
deco-present: "&aGeschenk"
|
||||
deco-candy_cane: "&cZuckerstange"
|
||||
deco-lantern_post: "&eLaternenpfahl"
|
||||
deco-tree: "&2Weihnachtsbaum"
|
||||
deco-wreath: "&4Kranz"
|
||||
deco-snowman: "&fSchneemann"
|
||||
deco-lights: "&6Lichterkette"
|
||||
deco-snow_pile: "&bSchneehaufen"
|
||||
deco-placed: "&aDeko aufgestellt. &7/xmas deco undo macht es rückgängig."
|
||||
deco-undone: "&7Letzte Deko entfernt."
|
||||
deco-nothing-undo: "&cNichts zum Rückgängigmachen."
|
||||
deco-look: "&cSchau auf einen Block."
|
||||
|
||||
# Login-Serie
|
||||
streak-reward: "&6Login-Serie: Tag &e%day% &6in Folge! Belohnung (Stufe &e%cycle%&6) erhalten."
|
||||
|
||||
# Grüße
|
||||
greeting-christmas-eve: "&6Heute ist Heiligabend! &fWir wünschen allen einen schönen Abend."
|
||||
greeting-new-years-eve: "&6Heute ist Silvester! &fRutscht gut ins neue Jahr."
|
||||
present-name: "&aGeschenk"
|
||||
|
||||
# Socken
|
||||
stocking-hung: "&aDeine Socke hängt am Kamin! Am Morgen des &e%date% &aliegt etwas darin."
|
||||
stocking-already: "&7Deine Socke hängt schon. Am &e%date% &7liegt etwas darin."
|
||||
stocking-too-early: "&cDu kannst deine Socke erst ab dem &e%from% &caufhängen."
|
||||
stocking-nothing: "&cDu hast dieses Jahr keine Socke aufgehängt."
|
||||
stocking-claimed: "&7Du hast deine Socke schon geleert."
|
||||
stocking-received: "&aIn deiner Socke lag etwas für dich! Frohe Weihnachten!"
|
||||
stocking-placed: "&aDeine Socke hängt jetzt an der Wand! Am Weihnachtstag fliegt Santa vorbei und legt ein Geschenk darunter."
|
||||
stocking-place-usage: "&cSchau auf eine freie Wand (Seitenfläche eines Blocks), um die Socke aufzuhängen."
|
||||
stocking-place-limit: "&cDu kannst höchstens &e%max% &cSocken aufstellen."
|
||||
stocking-removed: "&7Socke entfernt."
|
||||
stocking-none-near: "&cIn der Nähe hängt keine Socke von dir."
|
||||
stocking-present-placed: "&aSanta hat ein Geschenk unter deine Socke gelegt! (x=%x%, y=%y%, z=%z%)"
|
||||
stocking-present-notyours: "&cDieses Geschenk gehört jemand anderem."
|
||||
stocking-present-opened: "&aDu hast dein Socken-Geschenk geöffnet. Frohe Weihnachten!"
|
||||
stocking-flight-started: "&aHo ho ho! Santa fliegt von Socke zu Socke und bringt Geschenke!"
|
||||
stocking-flight-ok: "&aSantas Socken-Flug wurde gestartet."
|
||||
stocking-flight-none: "&cKein Flug möglich: Keine aufgestellten Socken in geladenen Chunks (oder ein Flug läuft schon)."
|
||||
|
||||
# Nikolausmütze
|
||||
hat-name: "&cNikolausmütze"
|
||||
hat-lore:
|
||||
- "&7Ho ho ho!"
|
||||
hat-offer: "&aMöchtest du eine Nikolausmütze?"
|
||||
hat-offer-click: "&e[Hier klicken]"
|
||||
hat-offer-hover: "&7Gibt dir eine Nikolausmütze"
|
||||
hat-given: "&aHo ho ho! Du hast eine Nikolausmütze bekommen."
|
||||
hat-have: "&7Du hast schon eine Nikolausmütze."
|
||||
hat-season: "&cDie Nikolausmütze gibt es nur in der Weihnachtszeit."
|
||||
|
||||
# Quiz
|
||||
quiz-question: "&6&lWeihnachtsquiz! &f%question% &7(%time%s)"
|
||||
quiz-win: "&a%player% &fhat es zuerst gewusst: &e%answer%"
|
||||
quiz-timeout: "&7Niemand wusste es. Die Antwort war: &e%answer%"
|
||||
quiz-reward: "&aDeine Belohnung wurde ausgegeben."
|
||||
quiz-usage: "&cNutzung: /xmas quiz start [Nr] | skip | count"
|
||||
quiz-already: "&cEs läuft schon eine Frage."
|
||||
quiz-none: "&cEs sind keine Quizfragen in der christmas.yml."
|
||||
quiz-stopped: "&7Frage beendet."
|
||||
quiz-not-active: "&cGerade läuft keine Frage."
|
||||
|
||||
# Wunschzettel
|
||||
wish-usage: "&cNutzung: /xmas wish <Text> | list | clear"
|
||||
wish-saved: "&aDein Wunsch steht auf dem Wunschzettel! Vielleicht erfüllt ihn der Weihnachtsmann."
|
||||
wish-limit: "&cDu hast schon genug offene Wünsche. (/xmas wish list, /xmas wish clear)"
|
||||
wish-too-long: "&cDer Wunsch ist zu lang (maximal %max% Zeichen)."
|
||||
wish-empty: "&7Du hast keine offenen Wünsche."
|
||||
wish-list-header: "&aDeine offenen Wünsche:"
|
||||
wish-list-line: "&7- &f%text%"
|
||||
wish-cleared: "&7%count% Wunsch/Wünsche gelöscht."
|
||||
wish-fulfilled: "&aDein Wunsch wurde erfüllt: &e%wish%"
|
||||
wish-fulfilled-gift: "&aDein Wunsch wurde erfüllt, ein Geschenk liegt im Inventar: &e%wish%"
|
||||
wish-admin-new: "&6Neuer Wunsch von &e%player%&6. Ansehen mit /xmas wishes"
|
||||
wish-admin-done: "&aWunsch von %player% erfüllt (ohne Geschenk)."
|
||||
wish-admin-done-gift: "&aWunsch von %player% erfüllt. Geschenk: &e%item%"
|
||||
wish-admin-done-offline: "&7%player% ist offline. Der Wunsch ist erfüllt, das Geschenk wird beim nächsten Join übergeben."
|
||||
wish-admin-hold: "&cHalte das Geschenk in der Hand und klicke dann links. (Shift+Linksklick erfüllt den Wunsch ohne Geschenk.)"
|
||||
wish-admin-deleted: "&7Wunsch von %player% gelöscht."
|
||||
wish-menu-title: "Wunschzettel"
|
||||
wish-menu-item-name: "&e%player%"
|
||||
wish-menu-hint-left: "&aLinksklick: erfüllen &7(Item in der Hand wird verschenkt)"
|
||||
wish-menu-hint-shift: "&eShift+Linksklick: ohne Geschenk erfüllen"
|
||||
wish-menu-hint-right: "&cRechtsklick: löschen"
|
||||
wish-page-prev: "&7Vorherige Seite"
|
||||
wish-page-next: "&7Nächste Seite"
|
||||
wish-menu-empty: "&7Keine offenen Wünsche"
|
||||
|
||||
# Marktstände
|
||||
market-usage: "&cNutzung: /xmas market spawn <id> | remove | list"
|
||||
market-unknown: "&cUnbekannter Stand: %id%"
|
||||
market-spawned: "&aMarktstand aufgestellt."
|
||||
market-removed: "&7Marktstand entfernt."
|
||||
market-none: "&cKein Marktstand in 6 Blöcken Umkreis."
|
||||
|
||||
# Portale und Weihnachtsmarkt
|
||||
market-unset: "&7Marktpunkt aufgehoben. Portale funktionieren erst wieder, wenn ein Admin ihn neu setzt."
|
||||
market-set: "&aWeihnachtsmarkt-Punkt gesetzt. Portale führen jetzt hierher."
|
||||
portal-usage: "&cNutzung: /xmas portal create | remove | info (schau auf den Boden bzw. aufs Portal)"
|
||||
portal-created: "&aDein Portal zum Weihnachtsmarkt steht! Geh hinein und warte kurz."
|
||||
portal-removed: "&7Portal entfernt."
|
||||
portal-none: "&cDu schaust kein Portal an."
|
||||
portal-not-owner: "&cDas ist nicht dein Portal."
|
||||
portal-limit: "&cDu hast schon %limit% Portal(e)."
|
||||
portal-need: "&cFür das Portal brauchst du Schneeblöcke: &f%cost%"
|
||||
portal-no-space: "&cHier ist nicht genug Platz (5 Blöcke breit, 5 hoch, fester Boden)."
|
||||
portal-no-market: "&cDer Weihnachtsmarkt wurde noch nicht von einem Admin festgelegt. Portale funktionieren erst danach."
|
||||
portal-world: "&cIn dieser Welt kannst du kein Portal bauen."
|
||||
portal-disabled: "&cPortale sind deaktiviert."
|
||||
portal-countdown: "&bPortal: &f%s% s"
|
||||
portal-teleported: "&aWillkommen auf dem Weihnachtsmarkt!"
|
||||
portal-back-click: "&e[Zurück zum Portal]"
|
||||
portal-back-ok: "&7Du bist zurück am Portal."
|
||||
portal-back-none: "&cEs ist kein Rückweg gespeichert."
|
||||
portal-info: "&aPortal-Kosten: &f%cost% &7- Deine Portale: &e%count%/%limit%"
|
||||
@@ -0,0 +1,276 @@
|
||||
messages:
|
||||
prefix: "&c[Christmas] &f"
|
||||
no-permission: "&cYou don't have permission."
|
||||
player-only: "&cThis command is for players only."
|
||||
player-offline: "&cPlayer not found."
|
||||
player-not-found: "&cPlayer not found."
|
||||
usage: "&cUsage: /christmas <help|snow|santa|give|gifts|addhand|snowman|countdown|cookie|wichteln|wreath|snowball|deco|stocking|hat|quiz|wish|wishes|market|portal|end|reload>"
|
||||
help:
|
||||
- "&2/xmas &7- version and status"
|
||||
- "&2/xmas snow &7- toggle snow for yourself"
|
||||
- "&2/xmas countdown &7- toggle the countdown bar"
|
||||
- "&2/xmas santa start [time] [world x y z | random] | stop | teleport"
|
||||
- "&2/xmas give <name> [amount] &7- give a Christmas crystal"
|
||||
- "&2/xmas gifts &7- spawn presents under every tree"
|
||||
- "&2/xmas addhand &7- add the held item as a gift"
|
||||
- "&2/xmas snowman spawn [stand|walk] [name] | remove &7- decorative snowman (standing/walking)"
|
||||
- "&2/xmas cookie list | give <player> <id> [amount]"
|
||||
- "&2/xmas wichteln join|leave|who|gift|status &7- secret santa"
|
||||
- "&2/xmas wichteln open|draw|deliver|reset &7- (admin)"
|
||||
- "&2/xmas wreath create|remove &7- advent wreath"
|
||||
- "&2/xmas snowball arena [radius] | start [time] | stop | top"
|
||||
- "&2/xmas deco [undo] &7- decoration menu"
|
||||
- "&2/xmas stocking [place | remove | flight] &7- hang up / open your stocking / place it on a wall"
|
||||
- "&2/xmas hat &7- Santa hat"
|
||||
- "&2/xmas wish <text> | list | clear &7- wish list"
|
||||
- "&2/xmas wishes &7- view wishes (admin)"
|
||||
- "&2/xmas quiz start [no.] | skip &7- Christmas quiz (admin)"
|
||||
- "&2/xmas market spawn <id> | remove | list &7- market stalls (admin)"
|
||||
- "&2/xmas portal create|remove|info &7- portal to the Christmas market"
|
||||
- "&2/xmas market set|unset|tp|back &7- market point (admin) / way back"
|
||||
- "&2/xmas end &7- end the event"
|
||||
- "&2/xmas reload &7- reload configuration"
|
||||
- "&2/cracker list | get [id] [amount] | give <player> [id] [amount]"
|
||||
info: "&aX-Mas module active. Status: &e%status% &7- Trees: &e%trees%"
|
||||
status-active: "ACTIVE"
|
||||
status-ended: "ENDED"
|
||||
reloaded: "&aChristmas configuration reloaded."
|
||||
past-day-restricted: "&cYou can't open past days any more."
|
||||
calendar-test-on: "&aCalendar test mode &eenabled&a: you can open any day."
|
||||
calendar-test-off: "&7Calendar test mode disabled."
|
||||
calendar-claimed-broadcast: "&e%player% &aclaimed the gift of calendar day &e%day%&a."
|
||||
calendar-claimed-self: "&aYou claimed the gift of day &e%day%&a."
|
||||
plugin-enabled: "&aMerry Christmas and a happy new year!"
|
||||
timeout: "&cThe holidays are over! See you next year!"
|
||||
final-wish: "&6Happy New Year, my friends!"
|
||||
end-set: "&7Event ended. Players can take down their trees and get their resources back."
|
||||
gifts-spawned: "&cHo! &2Ho! &cHo! &3Time to check the presents!"
|
||||
addhand-ok: "&aItem added as a gift."
|
||||
addhand-empty: "&cYou aren't holding an item."
|
||||
crystal-given: "&aYou received &e%amount%x Christmas crystal&a."
|
||||
crystal-gave: "&a%player% received %amount% crystal(s)."
|
||||
|
||||
# Snow
|
||||
snow-on: "&aSnow is now &eenabled &afor you."
|
||||
snow-off: "&7Snow is now &cdisabled &7for you."
|
||||
snow-disabled-global: "&cSnow is disabled on this server."
|
||||
|
||||
# Snowmen
|
||||
snowman-usage: "&cUsage: /christmas snowman spawn [stand|walk] [name] | remove"
|
||||
snowman-spawned: "&aDecorative snowman placed."
|
||||
snowman-removed: "&7Decorative snowman removed."
|
||||
snowman-none: "&cNo decorative snowman within 6 blocks."
|
||||
snowman-built: "&aYour snowman will give you a gift now and then! (max. %max% snowmen)"
|
||||
snowman-limit: "&cYou already have %max% snowmen, this one won't give gifts."
|
||||
|
||||
# Santa
|
||||
santa-started: "&aThe Santa Claus event has started! Santa is in &e%world% &aat &ex=%x%, y=%y%, z=%z% &aand waits &e%duration%&a. (/christmas santa teleport)"
|
||||
santa-started-sender: "&aYou started the Santa Claus event for &e%duration%&a."
|
||||
santa-dropped: "&aSanta has just dropped a gift!"
|
||||
santa-stopped: "&7Santa has moved on."
|
||||
santa-already: "&cSanta is already active."
|
||||
santa-not-active: "&cSanta is not active right now."
|
||||
santa-no-locations: "&cNo Santa locations are set in christmas.yml."
|
||||
santa-usage: "&cUsage: /christmas santa start [time] [world x y z | random] | stop | teleport"
|
||||
santa-bossbar: "&cSanta Claus &f- &e%time%s left"
|
||||
santa-teleported: "&aYou were teleported to Santa."
|
||||
|
||||
# Crackers
|
||||
cracker-given: "&aYou received &e%amount%x %id%&a."
|
||||
cracker-gave: "&a%player% received %amount%x %id%."
|
||||
cracker-unknown: "&cUnknown cracker: %id%"
|
||||
cracker-opened: "&6Bang! &fYou opened a cracker."
|
||||
cracker-usage: "&cUsage: /cracker list | get [id] [amount] | give <player> [id] [amount]"
|
||||
|
||||
# Magic tree
|
||||
tree:
|
||||
grow-lvl-ready: "&aWoosh! Right-click the tree to let it grow!"
|
||||
grow-req-list-title: "&eRequired (throw the items next to the tree):"
|
||||
grow-not-enough-place: "&cNot enough space to grow here."
|
||||
grow-lvl-max: "&6This tree has reached its maximum level!"
|
||||
grow-fed: "&7The tree absorbed &e%amount%x %item%&7."
|
||||
tree-limit: "&cDon't be greedy! (tree limit reached)"
|
||||
tree-planted: "&aYour Magic Tree was planted! Throw the required items next to it."
|
||||
tree-disabled: "&cMagic Trees are ended/disabled."
|
||||
destroy-sapling: "&cThis miracle won't come back. Really destroy it? Break it again to confirm."
|
||||
destroy-leaves-santa: "&cSanta sees everything."
|
||||
destroy-leaves-tut: "&7To destroy the tree, cut down the trunk."
|
||||
destroy-warning: "&cAre you sure? Your progress will be lost, no resources back. Break it again to confirm."
|
||||
destroy-fail-owner: "&cThis is not your tree!"
|
||||
destroy-complete: "&4You monster!"
|
||||
destroy-refund: "&aThe tree was taken down, your resources were returned."
|
||||
not-owner: "&cThis is not your tree."
|
||||
info: "&aMagic Tree of &e%owner%&a, level: &e%level%"
|
||||
gift:
|
||||
unluck-message: "&7Maybe Santa put you on his naughty list. Better luck next time!"
|
||||
opened: "&aYou opened a gift!"
|
||||
|
||||
# Countdown
|
||||
countdown-bossbar: "&cOnly &e%days% &cdays until Christmas Eve"
|
||||
countdown-bossbar-one: "&cOnly &e1 &cday until Christmas Eve"
|
||||
countdown-not-yet: "&7%days% days until Christmas Eve. The display starts in &e%in% &7day(s)."
|
||||
countdown-today: "&aIt's Christmas Eve! Merry Christmas!"
|
||||
countdown-on: "&aCountdown bar enabled."
|
||||
countdown-off: "&7Countdown bar disabled."
|
||||
|
||||
# Cookies
|
||||
cookie-usage: "&cUsage: /xmas cookie list | give <player> <id> [amount]"
|
||||
cookie-unknown: "&cUnknown cookie: %id%"
|
||||
cookie-given: "&aYou received &e%amount%x %id%&a."
|
||||
|
||||
# Secret Santa
|
||||
wi-open: "&aSecret Santa is open! Join with &e/xmas wichteln join&a."
|
||||
wi-joined: "&aYou joined the Secret Santa!"
|
||||
wi-already: "&7You are already signed up."
|
||||
wi-left: "&7You left the Secret Santa."
|
||||
wi-not-joined: "&cYou are not part of the Secret Santa."
|
||||
wi-wrong-state: "&cThat is not possible right now (wrong phase)."
|
||||
wi-too-few: "&cAt least 3 participants are needed."
|
||||
wi-drawn: "&aDrawn! Everyone has a partner. Check with &e/xmas wichteln who &ayour gift receiver."
|
||||
wi-who: "&aYou give a gift to: &e%name%"
|
||||
wi-gift-empty: "&cHold the gift in your hand."
|
||||
wi-gift-saved: "&aYour gift for &e%name% &ais saved. (Hold another item and repeat to replace it.)"
|
||||
wi-delivered: "&6The Secret Santa gifts were handed out! (&e%count% &6gifts)"
|
||||
wi-received: "&aYou received a Secret Santa gift!"
|
||||
wi-received-from: "&aYou received a Secret Santa gift from &e%from%&a!"
|
||||
wi-reset: "&7Secret Santa reset."
|
||||
wi-status: "&aSecret Santa: &e%state% &7- participants: &e%count% &7- gifts: &e%gifts%"
|
||||
|
||||
# Advent wreath
|
||||
wreath-created: "&aAdvent wreath built."
|
||||
wreath-removed: "&7Advent wreath removed."
|
||||
wreath-none: "&cNo advent wreath nearby."
|
||||
wreath-usage: "&cUsage: /xmas wreath create | remove (look at the ground)"
|
||||
wreath-advent: "&6Today is the &e%n%. Advent&6! &e%n% &6candles are burning on the advent wreath."
|
||||
|
||||
# Snowball game
|
||||
sb-usage: "&cUsage: /xmas snowball arena [radius] | start [time] | stop | top"
|
||||
sb-arena-set: "&aSnowball arena set (radius &e%radius%&a)."
|
||||
sb-no-arena: "&cNo arena set yet (/xmas snowball arena)."
|
||||
sb-already: "&cThe snowball game is already running."
|
||||
sb-not-active: "&cThe snowball game is not running."
|
||||
sb-started: "&b&lSnowball fight! &7For &e%time% &7in the arena (&e%world% %x% %z%&7). Throw snowballs at other players!"
|
||||
sb-ended: "&b&lSnowball fight over! &7The best:"
|
||||
sb-top-line: "&e%rank%. &f%player% &7- &a%points% points"
|
||||
sb-no-scores: "&7No points yet."
|
||||
sb-actionbar: "&fPoints: &a%points% &7| Rank &e%rank% &7| &c%time%s &7left"
|
||||
sb-reward: "&aCongratulations on place %rank%! Your reward was handed out."
|
||||
|
||||
# Decoration
|
||||
deco-menu-title: "Christmas decoration"
|
||||
deco-click-hint: "&7Click to place it where you are looking."
|
||||
deco-present: "&aGift"
|
||||
deco-candy_cane: "&cCandy cane"
|
||||
deco-lantern_post: "&eLantern post"
|
||||
deco-tree: "&2Christmas tree"
|
||||
deco-wreath: "&4Wreath"
|
||||
deco-snowman: "&fSnowman"
|
||||
deco-lights: "&6String lights"
|
||||
deco-snow_pile: "&bSnow pile"
|
||||
deco-placed: "&aDecoration placed. &7/xmas deco undo reverts it."
|
||||
deco-undone: "&7Last decoration removed."
|
||||
deco-nothing-undo: "&cNothing to undo."
|
||||
deco-look: "&cLook at a block."
|
||||
|
||||
# Login streak
|
||||
streak-reward: "&6Login streak: day &e%day% &6in a row! Reward (step &e%cycle%&6) received."
|
||||
|
||||
# Greetings
|
||||
greeting-christmas-eve: "&6Today is Christmas Eve! &fWe wish everyone a lovely evening."
|
||||
greeting-new-years-eve: "&6Today is New Year's Eve! &fHave a great start into the new year."
|
||||
present-name: "&aGift"
|
||||
|
||||
# Stockings
|
||||
stocking-hung: "&aYour stocking is hanging by the fireplace! On &e%date% &asomething will be inside."
|
||||
stocking-already: "&7Your stocking is already hanging. On &e%date% &7there will be something inside."
|
||||
stocking-too-early: "&cYou can hang your stocking from &e%from% &con."
|
||||
stocking-nothing: "&cYou didn't hang up a stocking this year."
|
||||
stocking-claimed: "&7You already emptied your stocking."
|
||||
stocking-received: "&aThere was something in your stocking! Merry Christmas!"
|
||||
stocking-placed: "&aYour stocking is now hanging on the wall! On Christmas Santa will fly by and leave a gift below it."
|
||||
stocking-place-usage: "&cLook at a free wall (the side of a block) to hang your stocking."
|
||||
stocking-place-limit: "&cYou can place at most &e%max% &cstockings."
|
||||
stocking-removed: "&7Stocking removed."
|
||||
stocking-none-near: "&cThere is none of your stockings nearby."
|
||||
stocking-present-placed: "&aSanta left a gift below your stocking! (x=%x%, y=%y%, z=%z%)"
|
||||
stocking-present-notyours: "&cThis gift belongs to someone else."
|
||||
stocking-present-opened: "&aYou opened your stocking gift. Merry Christmas!"
|
||||
stocking-flight-started: "&aHo ho ho! Santa is flying from stocking to stocking, bringing gifts!"
|
||||
stocking-flight-ok: "&aSanta's stocking flight has started."
|
||||
stocking-flight-none: "&cNo flight possible: no placed stockings in loaded chunks (or a flight is already running)."
|
||||
|
||||
# Santa hat
|
||||
hat-name: "&cSanta hat"
|
||||
hat-lore:
|
||||
- "&7Ho ho ho!"
|
||||
hat-offer: "&aWould you like a Santa hat?"
|
||||
hat-offer-click: "&e[Click here]"
|
||||
hat-offer-hover: "&7Gives you a Santa hat"
|
||||
hat-given: "&aHo ho ho! You got a Santa hat."
|
||||
hat-have: "&7You already have a Santa hat."
|
||||
hat-season: "&cThe Santa hat is only available during the holiday season."
|
||||
|
||||
# Quiz
|
||||
quiz-question: "&6&lChristmas quiz! &f%question% &7(%time%s)"
|
||||
quiz-win: "&a%player% &fknew it first: &e%answer%"
|
||||
quiz-timeout: "&7Nobody knew it. The answer was: &e%answer%"
|
||||
quiz-reward: "&aYour reward was handed out."
|
||||
quiz-usage: "&cUsage: /xmas quiz start [no.] | skip | count"
|
||||
quiz-already: "&cA question is already running."
|
||||
quiz-none: "&cThere are no quiz questions in christmas.yml."
|
||||
quiz-stopped: "&7Question ended."
|
||||
quiz-not-active: "&cNo question is running."
|
||||
|
||||
# Wish list
|
||||
wish-usage: "&cUsage: /xmas wish <text> | list | clear"
|
||||
wish-saved: "&aYour wish is on the wish list! Maybe Santa will fulfil it."
|
||||
wish-limit: "&cYou already have enough open wishes. (/xmas wish list, /xmas wish clear)"
|
||||
wish-too-long: "&cThe wish is too long (max. %max% characters)."
|
||||
wish-empty: "&7You have no open wishes."
|
||||
wish-list-header: "&aYour open wishes:"
|
||||
wish-list-line: "&7- &f%text%"
|
||||
wish-cleared: "&7%count% wish(es) deleted."
|
||||
wish-fulfilled: "&aYour wish was fulfilled: &e%wish%"
|
||||
wish-fulfilled-gift: "&aYour wish was fulfilled, a gift is in your inventory: &e%wish%"
|
||||
wish-admin-new: "&6New wish from &e%player%&6. View with /xmas wishes"
|
||||
wish-admin-done: "&aWish of %player% fulfilled (without a gift)."
|
||||
wish-admin-done-gift: "&aWish of %player% fulfilled. Gift: &e%item%"
|
||||
wish-admin-done-offline: "&7%player% is offline. The wish is fulfilled, the gift will be handed over on their next join."
|
||||
wish-admin-hold: "&cHold the gift in your hand and then left-click. (Shift+left-click fulfils the wish without a gift.)"
|
||||
wish-admin-deleted: "&7Wish of %player% deleted."
|
||||
wish-menu-title: "Wish list"
|
||||
wish-menu-item-name: "&e%player%"
|
||||
wish-menu-hint-left: "&aLeft-click: fulfil &7(the item in your hand is given away)"
|
||||
wish-menu-hint-shift: "&eShift+left-click: fulfil without a gift"
|
||||
wish-menu-hint-right: "&cRight-click: delete"
|
||||
wish-page-prev: "&7Previous page"
|
||||
wish-page-next: "&7Next page"
|
||||
wish-menu-empty: "&7No open wishes"
|
||||
|
||||
# Market stalls
|
||||
market-usage: "&cUsage: /xmas market spawn <id> | remove | list"
|
||||
market-unknown: "&cUnknown stall: %id%"
|
||||
market-spawned: "&aMarket stall placed."
|
||||
market-removed: "&7Market stall removed."
|
||||
market-none: "&cNo market stall within 6 blocks."
|
||||
|
||||
# Portals and Christmas market
|
||||
market-unset: "&7Market point removed. Portals work again once an admin sets it anew."
|
||||
market-set: "&aChristmas market point set. Portals now lead here."
|
||||
portal-usage: "&cUsage: /xmas portal create | remove | info (look at the ground / at the portal)"
|
||||
portal-created: "&aYour portal to the Christmas market is ready! Step in and wait a moment."
|
||||
portal-removed: "&7Portal removed."
|
||||
portal-none: "&cYou are not looking at a portal."
|
||||
portal-not-owner: "&cThis is not your portal."
|
||||
portal-limit: "&cYou already have %limit% portal(s)."
|
||||
portal-need: "&cYou need snow blocks for the portal: &f%cost%"
|
||||
portal-no-space: "&cNot enough space here (5 blocks wide, 5 high, solid ground)."
|
||||
portal-no-market: "&cThe Christmas market has not been set by an admin yet. Portals only work afterwards."
|
||||
portal-world: "&cYou can't build a portal in this world."
|
||||
portal-disabled: "&cPortals are disabled."
|
||||
portal-countdown: "&bPortal: &f%s% s"
|
||||
portal-teleported: "&aWelcome to the Christmas market!"
|
||||
portal-back-click: "&e[Back to the portal]"
|
||||
portal-back-ok: "&7You are back at the portal."
|
||||
portal-back-none: "&cNo way back is saved."
|
||||
portal-info: "&aPortal cost: &f%cost% &7- Your portals: &e%count%/%limit%"
|
||||
Reference in New Issue
Block a user