Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
069be7c33b | ||
|
|
950c1fbf47 |
@@ -607,6 +607,7 @@ Ab Version 1.0.3 bringt das Plugin zusätzlich viele Weihnachts-Funktionen mit.
|
||||
|
||||
- `/xmas snow` schaltet den Schnee **für dich selbst** an oder aus.
|
||||
- Einstellungen unter `snow:`: Stärke (`level` 1 bis 5), Welten sperren (`disable-in-worlds`), Standard an/aus (`default-on`).
|
||||
- **Schnee-Blacklist für Welten (Admin):** `/xmas snow world add <Welt>` sperrt den Schnee in einer Welt, `/xmas snow world remove <Welt>` hebt die Sperre auf, `/xmas snow world list` zeigt die gesperrten Welten. Die Liste steht in `snow.disable-in-worlds` und wird dauerhaft gespeichert, die Wirkung ist sofort.
|
||||
- Mit `enable-only-in-regions: true` und einer Liste unter `regions:` gibt es Schnee nur in bestimmten **WorldGuard**-Regionen. Dafür muss WorldGuard installiert sein.
|
||||
- Bei vielen Spielern kannst du `display-task-interval` erhöhen (z. B. 10), dann gibt es weniger Last.
|
||||
|
||||
@@ -625,6 +626,7 @@ Zeit geht als `300`, `30s`, `5m` oder `1h`. Ohne Angabe gilt `santa.duration`.
|
||||
- **Melodie:** Wenn Santa kommt, spielt eine Weihnachtsmelodie mit Glocken (Notenblock-Klänge, keine Dateien nötig). Einstellbar unter `santa.melody` (`enable`, `volume`, `tempo`, `everyone`).
|
||||
- **Geschenke:** `drop-interval` (Sekunden), `drop-chance` (in %), `radius` und die Liste `gifts`. Cracker gibst du als `cracker:cracker:1` an.
|
||||
- **Spazieren:** Mit `walk-radius` läuft Santa herum und hinterlässt eine Schneespur (`snow-trail`), die wieder verschwindet.
|
||||
Er läuft flüssig mit sanften Drehungen, `walk-speed` stellt das Tempo ein. Beim Laufen schwingen Beine und Arme, beim Stehenbleiben winkt er (`walk-animation`, `wave`).
|
||||
- **Aussehen:** Ab **Minecraft 1.21.9** ist Santa ein Spielermodell, davor ein Rüstungsständer. Einen eigenen Skin trägst du bei `skin-url` (Textur-Adresse von `textures.minecraft.net`) oder `skin-player` (Spielername) ein. Mit `model: ARMOR_STAND` erzwingst du den Rüstungsständer.
|
||||
- **Zufällige Orte:** Unter `locations:` trägst du Welt und Koordinaten ein (siehe Beispiel in der Datei).
|
||||
|
||||
@@ -657,6 +659,8 @@ So funktioniert der Baum:
|
||||
3. **Füttern:** Wirf die Items, die der Baum braucht, **neben den Baum**. Er nimmt sie auf (nur vom Besitzer). Mit Rechtsklick auf den Baum siehst du, was noch fehlt.
|
||||
4. **Wachsen:** Sind alle Items da, kommt eine Meldung und ein Ring leuchtet am Boden. Rechtsklick auf den Baum, dann wächst er eine Stufe.
|
||||
5. **Stufen:** Setzling, kleiner Baum, Baum, Magic Tree. Items und Wartezeiten stehen unter `magic-tree.levels`.
|
||||
6. **Geschenke ab Stufe:** `magic-tree.presents-from-level` legt fest, ab welcher Stufe Geschenke entstehen (`SAPLING`, `SMALL_TREE`, `TREE` oder `MAGIC_TREE`, Standard `SMALL_TREE`). Darunter gibt es keine Geschenke. Ab dieser Stufe gilt der `gift-cooldown` der Stufe, bei `-1` der Wert `default-gift-cooldown` (Standard 300 Sekunden).
|
||||
7. **Deko-Köpfe am Baum:** Statt Beton-, Gold- und Seelaternen-Blöcken hängen Köpfe im Baum (`magic-tree.ornaments.mode: heads`, zufällig gedreht). Die Texturen stehen unter `ornaments.heads`: entweder eine URL von textures.minecraft.net oder der „Value“ von minecraft-heads.com. Mit `mode: blocks` gibt es wieder die alten Blöcke. Wie viel Schmuck hängt, stellst du mit `ornaments.per-ring` (Stück pro Ring, Standard 3) und `ornaments.every-nth-layer` (nur jede n-te Ebene, Standard 2) ein. Schon gebaute Bäume behalten ihren Schmuck, bis sie die nächste Stufe erreichen.
|
||||
6. **Geschenke:** Ab dem kleinen Baum erscheinen Geschenk-Köpfe rund um den Baum. Rechtsklick oder Abbauen öffnet sie.
|
||||
|
||||
Standard-Kosten:
|
||||
@@ -786,9 +790,12 @@ quiz:
|
||||
| `/xmas market set` | **Marktplatz** an deiner Stelle festlegen (Ziel der Portale) |
|
||||
| `/xmas market unset` | Marktplatz aufheben, Portale führen dann nirgendwohin |
|
||||
| `/xmas market tp` | Zum Marktplatz teleportieren |
|
||||
| `/xmas market spawn <id>` | Marktstand aufstellen (mitgeliefert: `plaetzchen`, `deko`) |
|
||||
| `/xmas market spawn <id>` | Marktstand aufstellen (mitgeliefert: `plaetzchen`, `deko`, `gluehwein`) |
|
||||
| `/xmas market remove` | Nächsten Stand entfernen |
|
||||
| `/xmas market list` | Stand-IDs anzeigen |
|
||||
| `/xmas market santa set` | **Santa** an deiner Stelle auf den Markt stellen (zum Beispiel vor einen Thron oder Sessel) |
|
||||
| `/xmas market santa remove` | Markt-Santa wieder entfernen |
|
||||
| `/xmas market santa reset` | Tagesgeschenke für alle zurücksetzen (zum Testen) |
|
||||
|
||||
Ein Stand ist ein Händler (Villager), bei dem Spieler Items tauschen. Die Angebote stehen unter `market.stalls.<id>.trades`. Nach einer Änderung lädst du mit `/xmas reload` neu.
|
||||
|
||||
@@ -802,6 +809,15 @@ market:
|
||||
- { give: "cookie:zimtstern:2", price: ["EMERALD:1"] }
|
||||
```
|
||||
|
||||
**Leben auf dem Markt:** Alles bezieht sich auf den Marktplatz (`market.radius`, Standard 45 Blöcke).
|
||||
|
||||
- **Begrüßung:** Wer den Markt betritt, bekommt einen Titel („Weihnachtsmarkt“) mit Glockenklang (`market.welcome.enable`).
|
||||
- **Musik:** Im Markt spielen leise Weihnachtsmelodien mit Glocken, dazwischen sind Pausen (`market.music`: `enable`, `volume`, `tempo`, `pause-seconds`).
|
||||
- **Schnee nur im Markt:** Mit `market.snow-only-in-market: true` fällt der Schnee nur im Markt. Das gilt erst, wenn ein Marktplatz gesetzt ist.
|
||||
- **Santa auf dem Markt:** `/xmas market santa set` stellt Santa an deine Stelle. Er dreht sich zu Spielern in der Nähe und winkt. Jeder Spieler bekommt per Rechtsklick **einmal pro Tag** ein Geschenk (`market.santa.rewards`). Die Tagesgrenze wird in `market-life.yml` gespeichert. Santa erscheint nur, solange Spieler in der Nähe sind, und kommt nach einem Neustart von selbst wieder.
|
||||
- **Wichtel:** Kleine Wichtel-Figuren laufen zwischen den Ständen herum (`market.elves`: `count`, `radius`, `speed`, `names`, eigener Skin möglich). Sie sind nur da, solange Spieler im Markt sind.
|
||||
- **Glühwein-Stand:** `/xmas market spawn gluehwein` stellt einen Stand mit Glühwein, Kinderpunsch und heißer Schokolade auf. Beim Trinken gibt es Effekte (Regeneration, Feuerresistenz, Tempo, Absorption) und Dampf-Partikel. Die Getränke stehen unter `cookies` und lassen sich wie Plätzchen verschenken (`cookie:gluehwein:2`).
|
||||
|
||||
**Portale (Spieler):** Sie funktionieren **nur, wenn ein Admin den Marktplatz festgelegt hat**.
|
||||
|
||||
- `/xmas portal create`: Schau auf den Boden und gib den Befehl ein. Das Plugin baut einen Torbogen aus **Schneeblöcken** (10 Stück, werden verbraucht, Admins zahlen nichts). Er braucht 5 Blöcke Breite und Höhe sowie festen Boden.
|
||||
|
||||
@@ -32,7 +32,7 @@ public final class ChristmasCommand implements CommandExecutor, TabCompleter {
|
||||
}
|
||||
switch (a[0].toLowerCase()) {
|
||||
case "help" -> mgr.rawList("help").forEach(s::sendMessage);
|
||||
case "snow" -> snow(s);
|
||||
case "snow" -> snow(s, a);
|
||||
case "santa" -> santa(s, a);
|
||||
case "snowman" -> snowman(s, a);
|
||||
case "countdown" -> {
|
||||
@@ -295,6 +295,15 @@ public final class ChristmasCommand implements CommandExecutor, TabCompleter {
|
||||
if (dest == null) { s.sendMessage(mgr.msg("portal-no-market")); return; }
|
||||
p.teleport(dest);
|
||||
}
|
||||
case "santa" -> {
|
||||
String op = a.length > 2 ? a[2].toLowerCase() : "";
|
||||
switch (op) {
|
||||
case "set" -> { mgr.marketLife().setSanta(p.getLocation()); s.sendMessage(mgr.msg("market-santa-set")); }
|
||||
case "remove" -> { mgr.marketLife().removeSantaSpot(); s.sendMessage(mgr.msg("market-santa-removed")); }
|
||||
case "reset" -> { mgr.marketLife().resetClaims(); s.sendMessage(mgr.msg("market-santa-reset")); }
|
||||
default -> s.sendMessage(mgr.msg("market-usage"));
|
||||
}
|
||||
}
|
||||
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; }
|
||||
@@ -375,13 +384,36 @@ public final class ChristmasCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
// ---------- Snow ----------
|
||||
|
||||
private void snow(CommandSender s) {
|
||||
private void snow(CommandSender s, String[] a) {
|
||||
if (a.length > 1 && a[1].equalsIgnoreCase("world")) { snowWorld(s, a); return; }
|
||||
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"));
|
||||
}
|
||||
|
||||
/** /xmas snow world add|remove|list [Welt]: Schnee-Blacklist der Welten. */
|
||||
private void snowWorld(CommandSender s, String[] a) {
|
||||
if (!admin(s)) return;
|
||||
String op = a.length > 2 ? a[2].toLowerCase() : "list";
|
||||
if (op.equals("list")) {
|
||||
List<String> l = mgr.snowBlockedWorlds();
|
||||
s.sendMessage(mgr.msg("snow-world-list", "%worlds%", l.isEmpty() ? "-" : String.join(", ", l)));
|
||||
return;
|
||||
}
|
||||
if (a.length < 4 || !(op.equals("add") || op.equals("remove"))) { s.sendMessage(mgr.msg("snow-world-usage")); return; }
|
||||
String name = a[3];
|
||||
org.bukkit.World w = Bukkit.getWorld(name);
|
||||
if (op.equals("add")) {
|
||||
if (w == null) { s.sendMessage(mgr.msg("snow-world-unknown", "%world%", name)); return; }
|
||||
mgr.setSnowWorldBlocked(w.getName(), true);
|
||||
s.sendMessage(mgr.msg("snow-world-added", "%world%", w.getName()));
|
||||
} else {
|
||||
mgr.setSnowWorldBlocked(w != null ? w.getName() : name, false);
|
||||
s.sendMessage(mgr.msg("snow-world-removed", "%world%", w != null ? w.getName() : name));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Santa ----------
|
||||
|
||||
private void santa(CommandSender s, String[] a) {
|
||||
@@ -487,6 +519,15 @@ public final class ChristmasCommand implements CommandExecutor, TabCompleter {
|
||||
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 == 3 && a[0].equalsIgnoreCase("market") && a[1].equalsIgnoreCase("santa")) {
|
||||
out.addAll(Arrays.asList("set", "remove", "reset"));
|
||||
} else if (a.length == 2 && a[0].equalsIgnoreCase("snow")) {
|
||||
out.add("world");
|
||||
} else if (a.length == 3 && a[0].equalsIgnoreCase("snow") && a[1].equalsIgnoreCase("world")) {
|
||||
out.addAll(Arrays.asList("add", "remove", "list"));
|
||||
} else if (a.length == 4 && a[0].equalsIgnoreCase("snow") && a[1].equalsIgnoreCase("world")) {
|
||||
if (a[2].equalsIgnoreCase("remove")) out.addAll(mgr.snowBlockedWorlds());
|
||||
else Bukkit.getWorlds().forEach(w -> out.add(w.getName()));
|
||||
} 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")) {
|
||||
|
||||
@@ -235,6 +235,17 @@ public final class ChristmasListener implements Listener {
|
||||
if (mgr.quiz().onChat(e.getPlayer(), e.getMessage())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onMarketFigureClick(org.bukkit.event.player.PlayerInteractEntityEvent e) {
|
||||
if (e.getHand() != EquipmentSlot.HAND) return;
|
||||
if (mgr.marketLife().onInteract(e.getPlayer(), e.getRightClicked())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onMarketFigureManipulate(org.bukkit.event.player.PlayerArmorStandManipulateEvent e) {
|
||||
if (mgr.marketLife().isFigure(e.getRightClicked())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onSnowmanTrail(org.bukkit.event.block.EntityBlockFormEvent e) {
|
||||
mgr.snowmen().onSnowForm(e);
|
||||
|
||||
@@ -44,6 +44,7 @@ public final class ChristmasManager {
|
||||
private QuizManager quiz;
|
||||
private WishManager wishes;
|
||||
private MarketManager market;
|
||||
private MarketLife marketLife;
|
||||
private PortalManager portals;
|
||||
private YamlConfiguration msgs;
|
||||
|
||||
@@ -73,6 +74,7 @@ public final class ChristmasManager {
|
||||
instance.quiz = new QuizManager(instance);
|
||||
instance.wishes = new WishManager(instance);
|
||||
instance.market = new MarketManager(instance);
|
||||
instance.marketLife = new MarketLife(instance);
|
||||
instance.portals = new PortalManager(instance);
|
||||
instance.crackers.reload();
|
||||
instance.trees.load();
|
||||
@@ -87,6 +89,7 @@ public final class ChristmasManager {
|
||||
instance.quiz.start();
|
||||
instance.wishes.load();
|
||||
instance.market.load();
|
||||
instance.marketLife.start();
|
||||
instance.portals.load();
|
||||
instance.startSnow();
|
||||
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
|
||||
@@ -111,6 +114,7 @@ public final class ChristmasManager {
|
||||
instance.trees.save();
|
||||
instance.trees.shutdown();
|
||||
instance.snowmen.stop();
|
||||
instance.marketLife.stop();
|
||||
instance.countdown.stop();
|
||||
instance.wichteln.save();
|
||||
instance.wreaths.stop();
|
||||
@@ -132,6 +136,7 @@ public final class ChristmasManager {
|
||||
instance.countdown.start();
|
||||
instance.quiz.start();
|
||||
instance.market.refreshAll();
|
||||
instance.marketLife.start();
|
||||
instance.startSnow();
|
||||
}
|
||||
|
||||
@@ -168,6 +173,7 @@ public final class ChristmasManager {
|
||||
public QuizManager quiz() { return quiz; }
|
||||
public WishManager wishes() { return wishes; }
|
||||
public MarketManager market() { return market; }
|
||||
public MarketLife marketLife() { return marketLife; }
|
||||
public PortalManager portals() { return portals; }
|
||||
|
||||
/** Baum-, Geschenk- und Kranzbloecke, die vor Zerstoerung geschuetzt sind. */
|
||||
@@ -316,20 +322,57 @@ public final class ChristmasManager {
|
||||
return now;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt eine Schneeschicht und setzt den Block darunter zurueck: Gras/Myzel/Podsol behalten sonst
|
||||
* den Zustand "verschneit" und sehen grau-weiss aus, weil ohne Physik-Update niemand sie aktualisiert.
|
||||
*/
|
||||
public static void removeSnowLayer(org.bukkit.block.Block b) {
|
||||
if (b.getType() != org.bukkit.Material.SNOW) return;
|
||||
b.setType(org.bukkit.Material.AIR, false);
|
||||
org.bukkit.block.Block below = b.getRelative(0, -1, 0);
|
||||
if (below.getBlockData() instanceof org.bukkit.block.data.Snowable s && s.isSnowy()) {
|
||||
s.setSnowy(false);
|
||||
below.setBlockData(s, false);
|
||||
}
|
||||
}
|
||||
|
||||
/** Steht die Welt auf der Schnee-Blacklist (snow.disable-in-worlds, Gross-/Kleinschreibung egal)? */
|
||||
public boolean isSnowWorldBlocked(String world) {
|
||||
for (String b : cfg.getStringList("snow.disable-in-worlds")) if (b.equalsIgnoreCase(world)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<String> snowBlockedWorlds() {
|
||||
return new java.util.ArrayList<>(cfg.getStringList("snow.disable-in-worlds"));
|
||||
}
|
||||
|
||||
/** Setzt eine Welt auf die Schnee-Blacklist bzw. nimmt sie herunter und speichert christmas.yml. */
|
||||
public void setSnowWorldBlocked(String world, boolean blocked) {
|
||||
List<String> list = snowBlockedWorlds();
|
||||
list.removeIf(x -> x.equalsIgnoreCase(world));
|
||||
if (blocked) list.add(world);
|
||||
cfg.set("snow.disable-in-worlds", list);
|
||||
try {
|
||||
cfg.save(new File(plugin.getDataFolder(), "christmas.yml"));
|
||||
} catch (java.io.IOException e) {
|
||||
plugin.getLogger().warning("christmas.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if (isSnowWorldBlocked(w.getName()) || !isSnowEnabledFor(p)) continue;
|
||||
if (!marketLife.allowsSnowAt(p.getLocation())) continue;
|
||||
var loc = p.getLocation();
|
||||
if (regionsOnly && !WorldGuardHook.inAnyRegion(loc, regions)) continue;
|
||||
for (int i = 0; i < level * 4; i++) {
|
||||
|
||||
@@ -1,75 +1,81 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if (mgr.cfg().getBoolean("cookies." + id + ".steam", false)) {
|
||||
var at = p.getEyeLocation().add(p.getLocation().getDirection().multiply(0.4));
|
||||
p.getWorld().spawnParticle(org.bukkit.Particle.CLOUD, at, 14, 0.25, 0.2, 0.25, 0.02);
|
||||
p.getWorld().spawnParticle(org.bukkit.Particle.HEART, at.clone().add(0, 0.4, 0), 3, 0.3, 0.2, 0.3, 0);
|
||||
p.getWorld().playSound(p.getLocation(), org.bukkit.Sound.BLOCK_BREWING_STAND_BREW, 0.6f, 1.4f);
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Skull;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.Rotatable;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.type.Leaves;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
@@ -318,6 +320,9 @@ public final class MagicTreeManager {
|
||||
Leaves leaves = (Leaves) Material.SPRUCE_LEAVES.createBlockData();
|
||||
leaves.setPersistent(true);
|
||||
Material[] swag = {Material.RED_CONCRETE, Material.GOLD_BLOCK, Material.SEA_LANTERN, Material.BLUE_CONCRETE};
|
||||
boolean heads = ornamentHeads();
|
||||
int perRing = Math.max(1, Math.min(12, mgr.cfg().getInt("magic-tree.ornaments.per-ring", 3)));
|
||||
int layerStep = Math.max(1, Math.min(6, mgr.cfg().getInt("magic-tree.ornaments.every-nth-layer", 2)));
|
||||
|
||||
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++) {
|
||||
@@ -333,8 +338,9 @@ public final class MagicTreeManager {
|
||||
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());
|
||||
if (lv != Level.SMALL_TREE && edge && isOrnamentSpot(dx, dz, y, r, perRing, layerStep)) {
|
||||
m.put(new Vector(dx, y, dz), heads ? Material.PLAYER_HEAD.createBlockData()
|
||||
: swag[Math.floorMod(dx + dz + y, swag.length)].createBlockData());
|
||||
} else {
|
||||
m.put(new Vector(dx, y, dz), leaves);
|
||||
}
|
||||
@@ -344,6 +350,19 @@ public final class MagicTreeManager {
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schmuck gleichmaessig verteilt: pro Ring per-ring Stueck, nur jede n-te Ebene, jede Ebene leicht
|
||||
* versetzt (ergibt eine Spirale) statt zufaellig an einer Seite.
|
||||
*/
|
||||
private static boolean isOrnamentSpot(int dx, int dz, int y, int r, int perRing, int layerStep) {
|
||||
if (Math.floorMod(y, layerStep) != 0) return false;
|
||||
double a = (Math.atan2(dz, dx) + Math.PI) / (2 * Math.PI); // 0..1 rund um den Stamm
|
||||
double phase = a * perRing + y * 0.45;
|
||||
double frac = phase - Math.floor(phase);
|
||||
int edgePerSector = Math.max(1, (int) Math.round(4.0 * r / perRing));
|
||||
return frac < 1.0 / edgePerSector;
|
||||
}
|
||||
|
||||
private boolean canGrow(Tree t, Level lv) {
|
||||
Location base = t.loc();
|
||||
if (base == null) return false;
|
||||
@@ -373,13 +392,65 @@ public final class MagicTreeManager {
|
||||
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);
|
||||
BlockData bd = e.getValue();
|
||||
if (bd.getMaterial() == Material.PLAYER_HEAD && bd.clone() instanceof Rotatable rot) {
|
||||
BlockFace[] faces = BlockFace.values();
|
||||
rot.setRotation(rotationFaces()[ChristmasManager.RANDOM.nextInt(rotationFaces().length)]);
|
||||
b.setBlockData(rot, false);
|
||||
List<String> urls = headUrls();
|
||||
if (!urls.isEmpty()) applyHead(b, urls.get(ChristmasManager.RANDOM.nextInt(urls.size())));
|
||||
} else {
|
||||
b.setBlockData(bd, false);
|
||||
}
|
||||
t.blocks.add(key(b));
|
||||
blockMap.put(key(b), t);
|
||||
}
|
||||
t.indexed = true;
|
||||
}
|
||||
|
||||
private static BlockFace[] rotationFaces() {
|
||||
return new BlockFace[]{BlockFace.NORTH, BlockFace.NORTH_NORTH_EAST, BlockFace.NORTH_EAST, BlockFace.EAST_NORTH_EAST,
|
||||
BlockFace.EAST, BlockFace.EAST_SOUTH_EAST, BlockFace.SOUTH_EAST, BlockFace.SOUTH_SOUTH_EAST,
|
||||
BlockFace.SOUTH, BlockFace.SOUTH_SOUTH_WEST, BlockFace.SOUTH_WEST, BlockFace.WEST_SOUTH_WEST,
|
||||
BlockFace.WEST, BlockFace.WEST_NORTH_WEST, BlockFace.NORTH_WEST, BlockFace.NORTH_NORTH_WEST};
|
||||
}
|
||||
|
||||
private static final java.util.Set<Material> ORNAMENTS = java.util.EnumSet.of(Material.RED_CONCRETE,
|
||||
Material.GOLD_BLOCK, Material.SEA_LANTERN, Material.BLUE_CONCRETE, Material.PLAYER_HEAD);
|
||||
|
||||
/** Schmuck oder Nadeln: aeltere Baeume haben die Verteilung anders, beides gehoert zum Baum. */
|
||||
private static boolean isTreeDecor(Material m) {
|
||||
return ORNAMENTS.contains(m) || m == Material.SPRUCE_LEAVES;
|
||||
}
|
||||
|
||||
/** Deko-Koepfe statt Betonbloecken (magic-tree.ornaments.mode = heads, wenn Texturen eingetragen sind). */
|
||||
private boolean ornamentHeads() {
|
||||
return "heads".equalsIgnoreCase(mgr.cfg().getString("magic-tree.ornaments.mode", "heads")) && !headUrls().isEmpty();
|
||||
}
|
||||
|
||||
/** Texturen der Deko-Koepfe als URL (auch Base64-"Value" von minecraft-heads.com ist erlaubt). */
|
||||
private List<String> headUrls() {
|
||||
List<String> out = new ArrayList<>();
|
||||
for (String s : mgr.cfg().getStringList("magic-tree.ornaments.heads")) {
|
||||
String u = textureUrl(s);
|
||||
if (u != null) out.add(u);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static String textureUrl(String s) {
|
||||
if (s == null) return null;
|
||||
s = s.trim();
|
||||
if (s.startsWith("http")) return s;
|
||||
try {
|
||||
String json = new String(java.util.Base64.getDecoder().decode(s), java.nio.charset.StandardCharsets.UTF_8);
|
||||
java.util.regex.Matcher m = java.util.regex.Pattern.compile("\"url\"\\s*:\\s*\"([^\"]+)\"").matcher(json);
|
||||
return m.find() ? m.group(1) : null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 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);
|
||||
@@ -388,7 +459,8 @@ public final class MagicTreeManager {
|
||||
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()) {
|
||||
Material expected = e.getValue().getMaterial();
|
||||
if (b.getType() == expected || (isTreeDecor(expected) && isTreeDecor(b.getType()))) {
|
||||
t.blocks.add(key(b));
|
||||
blockMap.put(key(b), t);
|
||||
}
|
||||
@@ -556,13 +628,22 @@ public final class MagicTreeManager {
|
||||
|
||||
// ================= Geschenke =================
|
||||
|
||||
/** Ab welcher Stufe Geschenke entstehen (magic-tree.presents-from-level, Standard SMALL_TREE). */
|
||||
private Level presentsFromLevel() {
|
||||
String s = mgr.cfg().getString("magic-tree.presents-from-level", "SMALL_TREE");
|
||||
try { return Level.valueOf(s.trim().toUpperCase()); } catch (Exception e) { return Level.SMALL_TREE; }
|
||||
}
|
||||
|
||||
private long cooldown(Level lv) {
|
||||
return mgr.cfg().getLong("magic-tree.levels." + lv.key + ".gift-cooldown", -1);
|
||||
if (lv.ordinal() < presentsFromLevel().ordinal()) return -1;
|
||||
long cd = mgr.cfg().getLong("magic-tree.levels." + lv.key + ".gift-cooldown", -1);
|
||||
// Ab der eingestellten Stufe gibt es immer Geschenke, auch wenn dort -1 steht
|
||||
return cd > 0 ? cd : Math.max(30, mgr.cfg().getLong("magic-tree.default-gift-cooldown", 300));
|
||||
}
|
||||
|
||||
public int spawnPresentsEverywhere() {
|
||||
int n = 0;
|
||||
for (Tree t : new ArrayList<>(trees)) if (t.level != Level.SAPLING && spawnPresent(t, true)) n++;
|
||||
for (Tree t : new ArrayList<>(trees)) if (t.level.ordinal() >= presentsFromLevel().ordinal() && spawnPresent(t, true)) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -596,6 +677,11 @@ public final class MagicTreeManager {
|
||||
|
||||
void setSkin(Block b, String url) {
|
||||
b.setType(Material.PLAYER_HEAD, false);
|
||||
applyHead(b, url);
|
||||
}
|
||||
|
||||
/** Setzt die Textur auf einen vorhandenen Kopf-Block (Drehung bleibt erhalten). */
|
||||
private void applyHead(Block b, String url) {
|
||||
if (!(b.getState() instanceof Skull sk)) return;
|
||||
try {
|
||||
PlayerProfile prof = Bukkit.createPlayerProfile(UUID.nameUUIDFromBytes(url.getBytes()), "present");
|
||||
|
||||
@@ -0,0 +1,481 @@
|
||||
package de.mviper.adventskalender.christmas;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.SoundCategory;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.ArmorStand;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.util.EulerAngle;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Leben auf dem Weihnachtsmarkt: Begruessung, Musik, Santa mit Tagesgeschenk und herumlaufende Wichtel.
|
||||
* Alles bezieht sich auf den Marktpunkt (/xmas market set).
|
||||
*/
|
||||
public final class MarketLife {
|
||||
private static final class Elf {
|
||||
LivingEntity entity;
|
||||
Location loc;
|
||||
Location target;
|
||||
int pause = 20;
|
||||
float yaw;
|
||||
double phase;
|
||||
}
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private final File file;
|
||||
private final NamespacedKey santaKey;
|
||||
private final NamespacedKey elfKey;
|
||||
private final SantaMelody music;
|
||||
|
||||
private Location santaLoc;
|
||||
private final Map<UUID, String> claimed = new HashMap<>();
|
||||
private LivingEntity santa;
|
||||
private final List<Elf> elves = new ArrayList<>();
|
||||
|
||||
private final Set<UUID> inside = new HashSet<>();
|
||||
private final Map<UUID, Long> welcomeAt = new HashMap<>();
|
||||
private final Map<UUID, Long> hintAt = new HashMap<>();
|
||||
private List<Player> audience = new ArrayList<>();
|
||||
private long musicNext;
|
||||
private int tune;
|
||||
private int tick;
|
||||
|
||||
private BukkitTask lifeTask;
|
||||
private BukkitTask moveTask;
|
||||
|
||||
MarketLife(ChristmasManager mgr) {
|
||||
this.mgr = mgr;
|
||||
this.file = new File(mgr.plugin().getDataFolder(), "market-life.yml");
|
||||
this.santaKey = new NamespacedKey(mgr.plugin(), "market_santa");
|
||||
this.elfKey = new NamespacedKey(mgr.plugin(), "market_elf");
|
||||
this.music = new SantaMelody(mgr);
|
||||
}
|
||||
|
||||
// ---------- Start / Stop / Speichern ----------
|
||||
|
||||
void start() {
|
||||
stop();
|
||||
load();
|
||||
lifeTask = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::life, 40L, 20L);
|
||||
moveTask = Bukkit.getScheduler().runTaskTimer(mgr.plugin(), this::move, 41L, 1L);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (lifeTask != null) lifeTask.cancel();
|
||||
if (moveTask != null) moveTask.cancel();
|
||||
lifeTask = null;
|
||||
moveTask = null;
|
||||
music.stop();
|
||||
removeSanta();
|
||||
removeElves();
|
||||
inside.clear();
|
||||
}
|
||||
|
||||
private void load() {
|
||||
santaLoc = null;
|
||||
claimed.clear();
|
||||
if (!file.exists()) return;
|
||||
YamlConfiguration y = YamlConfiguration.loadConfiguration(file);
|
||||
World w = Bukkit.getWorld(y.getString("santa.world", ""));
|
||||
if (w != null) {
|
||||
santaLoc = new Location(w, y.getDouble("santa.x"), y.getDouble("santa.y"), y.getDouble("santa.z"),
|
||||
(float) y.getDouble("santa.yaw"), 0f);
|
||||
}
|
||||
ConfigurationSection c = y.getConfigurationSection("claimed");
|
||||
if (c != null) {
|
||||
for (String k : c.getKeys(false)) {
|
||||
try { claimed.put(UUID.fromString(k), c.getString(k, "")); } catch (IllegalArgumentException ignored) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void save() {
|
||||
YamlConfiguration y = new YamlConfiguration();
|
||||
if (santaLoc != null && santaLoc.getWorld() != null) {
|
||||
y.set("santa.world", santaLoc.getWorld().getName());
|
||||
y.set("santa.x", santaLoc.getX());
|
||||
y.set("santa.y", santaLoc.getY());
|
||||
y.set("santa.z", santaLoc.getZ());
|
||||
y.set("santa.yaw", santaLoc.getYaw());
|
||||
}
|
||||
// Nur heutige Eintraege behalten, die Datei bleibt klein
|
||||
String today = today();
|
||||
for (var e : claimed.entrySet()) if (today.equals(e.getValue())) y.set("claimed." + e.getKey(), e.getValue());
|
||||
try {
|
||||
y.save(file);
|
||||
} catch (IOException e) {
|
||||
mgr.plugin().getLogger().warning("market-life.yml konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String today() { return LocalDate.now(mgr.plugin().getZone()).toString(); }
|
||||
|
||||
// ---------- Bereich ----------
|
||||
|
||||
private Location center() { return mgr.market().spawn(); }
|
||||
|
||||
boolean inside(Location l) {
|
||||
Location c = center();
|
||||
if (c == null || l == null || l.getWorld() == null || !l.getWorld().equals(c.getWorld())) return false;
|
||||
double r = Math.max(5, mgr.cfg().getDouble("market.radius", 45));
|
||||
double dx = l.getX() - c.getX(), dz = l.getZ() - c.getZ();
|
||||
return dx * dx + dz * dz <= r * r && Math.abs(l.getY() - c.getY()) <= 40;
|
||||
}
|
||||
|
||||
/** Schnee faellt nur im Markt, wenn market.snow-only-in-market gilt und ein Marktpunkt gesetzt ist. */
|
||||
boolean allowsSnowAt(Location l) {
|
||||
if (!mgr.cfg().getBoolean("market.snow-only-in-market", true)) return true;
|
||||
if (center() == null) return true;
|
||||
return inside(l);
|
||||
}
|
||||
|
||||
boolean isFigure(Entity e) {
|
||||
var pdc = e.getPersistentDataContainer();
|
||||
return pdc.has(santaKey, PersistentDataType.BYTE) || pdc.has(elfKey, PersistentDataType.BYTE);
|
||||
}
|
||||
|
||||
// ---------- Sekundentakt ----------
|
||||
|
||||
private void life() {
|
||||
Location c = center();
|
||||
List<Player> in = new ArrayList<>();
|
||||
if (c != null) for (Player p : Bukkit.getOnlinePlayers()) if (inside(p.getLocation())) in.add(p);
|
||||
audience = in;
|
||||
|
||||
// Begruessung beim Betreten
|
||||
Set<UUID> ids = new HashSet<>();
|
||||
for (Player p : in) {
|
||||
ids.add(p.getUniqueId());
|
||||
if (!inside.contains(p.getUniqueId())) enter(p);
|
||||
}
|
||||
inside.retainAll(ids);
|
||||
inside.addAll(ids);
|
||||
|
||||
// Musik
|
||||
if (in.isEmpty()) {
|
||||
music.stop();
|
||||
} else if (mgr.cfg().getBoolean("market.music.enable", true) && !music.playing()
|
||||
&& System.currentTimeMillis() >= musicNext) {
|
||||
final float vol = (float) mgr.cfg().getDouble("market.music.volume", 0.35);
|
||||
final int tempo = Math.max(1, mgr.cfg().getInt("market.music.tempo", 3));
|
||||
final long pause = Math.max(5, mgr.cfg().getLong("market.music.pause-seconds", 40)) * 1000L;
|
||||
music.play(() -> audience, tune++, vol, tempo, () -> musicNext = System.currentTimeMillis() + pause);
|
||||
}
|
||||
|
||||
ensureSanta();
|
||||
ensureElves(!in.isEmpty());
|
||||
santaHints();
|
||||
}
|
||||
|
||||
private void enter(Player p) {
|
||||
if (!mgr.cfg().getBoolean("market.welcome.enable", true)) return;
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = welcomeAt.get(p.getUniqueId());
|
||||
if (last != null && now - last < 60_000L) return;
|
||||
welcomeAt.put(p.getUniqueId(), now);
|
||||
p.sendTitle(mgr.raw("market-welcome-title"), mgr.raw("market-welcome-subtitle"), 10, 70, 20);
|
||||
float[] pitches = {1.0f, 1.26f, 1.5f, 2.0f};
|
||||
for (int i = 0; i < pitches.length; i++) {
|
||||
final float pitch = pitches[i];
|
||||
Bukkit.getScheduler().runTaskLater(mgr.plugin(), () -> {
|
||||
if (p.isOnline()) p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BELL, SoundCategory.RECORDS, 1.2f, pitch);
|
||||
}, i * 4L);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Santa auf dem Markt ----------
|
||||
|
||||
public void setSanta(Location l) {
|
||||
santaLoc = l.clone();
|
||||
santaLoc.setPitch(0f);
|
||||
save();
|
||||
removeSanta();
|
||||
ensureSanta();
|
||||
}
|
||||
|
||||
public void removeSantaSpot() {
|
||||
santaLoc = null;
|
||||
save();
|
||||
removeSanta();
|
||||
}
|
||||
|
||||
public void resetClaims() {
|
||||
claimed.clear();
|
||||
save();
|
||||
}
|
||||
|
||||
private void ensureSanta() {
|
||||
if (santaLoc == null || santaLoc.getWorld() == null || !mgr.cfg().getBoolean("market.santa.enable", true)) {
|
||||
removeSanta();
|
||||
return;
|
||||
}
|
||||
World w = santaLoc.getWorld();
|
||||
boolean near = false;
|
||||
if (w.isChunkLoaded(santaLoc.getBlockX() >> 4, santaLoc.getBlockZ() >> 4)) {
|
||||
for (Player p : w.getPlayers()) {
|
||||
if (p.getLocation().distanceSquared(santaLoc) <= 56 * 56) { near = true; break; }
|
||||
}
|
||||
}
|
||||
if (!near) { removeSanta(); return; }
|
||||
if (santa != null && santa.isValid()) return;
|
||||
try {
|
||||
LivingEntity e = mgr.santa().createModel(santaLoc.clone());
|
||||
e.setCustomName(ChristmasManager.color(mgr.cfg().getString("market.santa.name", "&c&lSanta Claus")));
|
||||
e.setGravity(false);
|
||||
e.setInvulnerable(true);
|
||||
e.setSilent(true);
|
||||
e.setPersistent(false);
|
||||
e.getPersistentDataContainer().set(santaKey, PersistentDataType.BYTE, (byte) 1);
|
||||
santa = e;
|
||||
} catch (Throwable t) {
|
||||
mgr.plugin().getLogger().warning("Markt-Santa konnte nicht erzeugt werden: " + t);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSanta() {
|
||||
if (santa != null) {
|
||||
try { santa.remove(); } catch (Throwable ignored) { }
|
||||
santa = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Hinweis in der Actionbar, wenn man bei Santa steht und das Tagesgeschenk noch offen ist. */
|
||||
private void santaHints() {
|
||||
if (santa == null || !santa.isValid()) return;
|
||||
long now = System.currentTimeMillis();
|
||||
for (Player p : santa.getWorld().getPlayers()) {
|
||||
if (p.getLocation().distanceSquared(santa.getLocation()) > 25) continue;
|
||||
if (today().equals(claimed.get(p.getUniqueId()))) continue;
|
||||
Long last = hintAt.get(p.getUniqueId());
|
||||
if (last != null && now - last < 8000L) continue;
|
||||
hintAt.put(p.getUniqueId(), now);
|
||||
p.spigot().sendMessage(net.md_5.bungee.api.ChatMessageType.ACTION_BAR,
|
||||
new net.md_5.bungee.api.chat.TextComponent(mgr.raw("market-santa-hint")));
|
||||
}
|
||||
}
|
||||
|
||||
/** Rechtsklick auf eine Markt-Figur. Gibt true zurueck, wenn die Figur uns gehoert (Ereignis abbrechen). */
|
||||
boolean onInteract(Player p, Entity e) {
|
||||
if (!isFigure(e)) return false;
|
||||
if (!e.getPersistentDataContainer().has(santaKey, PersistentDataType.BYTE)) return true; // Wichtel: nichts
|
||||
if (!p.hasPermission("xmas.use")) return true;
|
||||
String today = today();
|
||||
if (today.equals(claimed.get(p.getUniqueId()))) {
|
||||
p.sendMessage(mgr.msg("market-santa-claimed"));
|
||||
return true;
|
||||
}
|
||||
claimed.put(p.getUniqueId(), today);
|
||||
save();
|
||||
for (Map<?, ?> reward : mgr.cfg().getMapList("market.santa.rewards")) ChristmasExtras.applyMap(p, reward);
|
||||
p.sendMessage(mgr.msg("market-santa-gift"));
|
||||
World w = e.getWorld();
|
||||
Location at = e.getLocation().add(0, 1.6, 0);
|
||||
w.spawnParticle(Particle.END_ROD, at, 25, 0.4, 0.4, 0.4, 0.05);
|
||||
w.spawnParticle(Particle.SNOWFLAKE, at, 20, 0.5, 0.5, 0.5, 0.03);
|
||||
w.playSound(at, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.3f);
|
||||
w.playSound(at, Sound.BLOCK_NOTE_BLOCK_BELL, SoundCategory.RECORDS, 1.5f, 1.5f);
|
||||
try { santa.swingMainHand(); } catch (Throwable ignored) { }
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------- Wichtel ----------
|
||||
|
||||
private void ensureElves(boolean playersInside) {
|
||||
Location c = center();
|
||||
elves.removeIf(el -> {
|
||||
if (el.entity != null && el.entity.isValid()) return false;
|
||||
return true;
|
||||
});
|
||||
if (c == null || !playersInside || !mgr.cfg().getBoolean("market.elves.enable", true)) {
|
||||
removeElves();
|
||||
return;
|
||||
}
|
||||
int count = Math.max(0, Math.min(20, mgr.cfg().getInt("market.elves.count", 4)));
|
||||
while (elves.size() > count) {
|
||||
Elf el = elves.remove(elves.size() - 1);
|
||||
el.entity.remove();
|
||||
}
|
||||
if (elves.size() < count) spawnElf(c); // pro Sekunde einer, das verteilt die Last
|
||||
}
|
||||
|
||||
private void spawnElf(Location c) {
|
||||
double radius = Math.max(3, mgr.cfg().getDouble("market.elves.radius", 20));
|
||||
World w = c.getWorld();
|
||||
for (int attempt = 0; attempt < 15; attempt++) {
|
||||
double ang = ChristmasManager.RANDOM.nextDouble() * Math.PI * 2;
|
||||
double dist = ChristmasManager.RANDOM.nextDouble() * radius;
|
||||
double x = c.getX() + Math.cos(ang) * dist, z = c.getZ() + Math.sin(ang) * dist;
|
||||
double gy = groundAt(w, x, z, c.getY());
|
||||
if (Double.isNaN(gy)) continue;
|
||||
List<String> names = mgr.cfg().getStringList("market.elves.names");
|
||||
String name = names.isEmpty() ? "&aWichtel" : names.get(ChristmasManager.RANDOM.nextInt(names.size()));
|
||||
Location at = new Location(w, x, gy, z, ChristmasManager.RANDOM.nextFloat() * 360f, 0f);
|
||||
try {
|
||||
LivingEntity e = mgr.santa().createFigure(at, ChristmasManager.color(name),
|
||||
mgr.cfg().getString("market.elves.skin-url", ""), mgr.cfg().getString("market.elves.skin-player", ""),
|
||||
Color.fromRGB(0x2E8B2E), true);
|
||||
e.setGravity(false);
|
||||
e.setInvulnerable(true);
|
||||
e.setSilent(true);
|
||||
e.setPersistent(false);
|
||||
e.getPersistentDataContainer().set(elfKey, PersistentDataType.BYTE, (byte) 1);
|
||||
Elf el = new Elf();
|
||||
el.entity = e;
|
||||
el.loc = at;
|
||||
el.yaw = at.getYaw();
|
||||
el.pause = 20 + ChristmasManager.RANDOM.nextInt(60);
|
||||
elves.add(el);
|
||||
} catch (Throwable t) {
|
||||
mgr.plugin().getLogger().warning("Wichtel konnte nicht erzeugt werden: " + t);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeElves() {
|
||||
for (Elf el : elves) {
|
||||
try { el.entity.remove(); } catch (Throwable ignored) { }
|
||||
}
|
||||
elves.clear();
|
||||
}
|
||||
|
||||
// ---------- Bewegung (jeden Tick) ----------
|
||||
|
||||
private void move() {
|
||||
tick++;
|
||||
for (Elf el : new ArrayList<>(elves)) walk(el);
|
||||
if (tick % 10 == 0) santaLook();
|
||||
if (tick % 160 == 0 && santa != null && santa.isValid() && !audience.isEmpty()) {
|
||||
try { santa.swingMainHand(); } catch (Throwable ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
/** Santa dreht sich langsam zum naechsten Spieler. */
|
||||
private void santaLook() {
|
||||
if (santa == null || !santa.isValid()) return;
|
||||
Player best = null;
|
||||
double bd = 64;
|
||||
for (Player p : santa.getWorld().getPlayers()) {
|
||||
double d = p.getLocation().distanceSquared(santa.getLocation());
|
||||
if (d < bd) { bd = d; best = p; }
|
||||
}
|
||||
if (best == null) return;
|
||||
Location l = santa.getLocation();
|
||||
Location face = l.clone();
|
||||
face.setDirection(best.getEyeLocation().toVector().subtract(l.toVector().add(new org.bukkit.util.Vector(0, 1.6, 0))));
|
||||
float diff = ((face.getYaw() - l.getYaw() + 540f) % 360f) - 180f;
|
||||
if (Math.abs(diff) < 8f) return;
|
||||
l.setYaw(l.getYaw() + Math.max(-40f, Math.min(40f, diff)));
|
||||
l.setPitch(Math.max(-30f, Math.min(30f, face.getPitch())));
|
||||
santa.teleport(l);
|
||||
}
|
||||
|
||||
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 walk(Elf el) {
|
||||
LivingEntity e = el.entity;
|
||||
if (e == null || !e.isValid()) return;
|
||||
Location c = center();
|
||||
if (c == null) return;
|
||||
if (el.pause > 0) {
|
||||
el.pause--;
|
||||
if (el.pause == 0) idle(el, false);
|
||||
else if (el.pause % 40 == 20) idle(el, true);
|
||||
return;
|
||||
}
|
||||
World w = c.getWorld();
|
||||
double radius = Math.max(3, mgr.cfg().getDouble("market.elves.radius", 20));
|
||||
if (el.target == null) {
|
||||
double ang = ChristmasManager.RANDOM.nextDouble() * Math.PI * 2;
|
||||
double dist = 2 + ChristmasManager.RANDOM.nextDouble() * 8;
|
||||
double tx = el.loc.getX() + Math.cos(ang) * dist, tz = el.loc.getZ() + Math.sin(ang) * dist;
|
||||
double cx = tx - c.getX(), cz = tz - c.getZ();
|
||||
if (cx * cx + cz * cz > radius * radius) { el.pause = 10; return; }
|
||||
double gy = groundAt(w, tx, tz, el.loc.getY());
|
||||
if (Double.isNaN(gy)) { el.pause = 10; return; }
|
||||
el.target = new Location(w, tx, gy, tz);
|
||||
}
|
||||
double dx = el.target.getX() - el.loc.getX(), dz = el.target.getZ() - el.loc.getZ();
|
||||
if (Math.sqrt(dx * dx + dz * dz) < 0.25) {
|
||||
el.target = null;
|
||||
el.pause = 40 + ChristmasManager.RANDOM.nextInt(120);
|
||||
return;
|
||||
}
|
||||
float want = (float) Math.toDegrees(Math.atan2(-dx, dz));
|
||||
float diff = ((want - el.yaw + 540f) % 360f) - 180f;
|
||||
el.yaw += Math.max(-12f, Math.min(12f, diff));
|
||||
if (Math.abs(diff) > 45f) return;
|
||||
|
||||
double speed = Math.max(0.03, mgr.cfg().getDouble("market.elves.speed", 0.1));
|
||||
double rad = Math.toRadians(el.yaw);
|
||||
double nx = el.loc.getX() - Math.sin(rad) * speed;
|
||||
double nz = el.loc.getZ() + Math.cos(rad) * speed;
|
||||
double gy = groundAt(w, nx, nz, el.loc.getY());
|
||||
if (Double.isNaN(gy) || Math.abs(gy - el.loc.getY()) > 1.01) {
|
||||
el.target = null;
|
||||
el.pause = 8 + ChristmasManager.RANDOM.nextInt(20);
|
||||
return;
|
||||
}
|
||||
double ny = el.loc.getY() + Math.max(-0.25, Math.min(0.25, gy - el.loc.getY()));
|
||||
Location next = new Location(w, nx, ny, nz, el.yaw, 0f);
|
||||
e.teleport(next);
|
||||
el.loc = next;
|
||||
if (e instanceof ArmorStand as) {
|
||||
el.phase += 0.4 + speed * 3;
|
||||
double a = Math.sin(el.phase) * 0.7;
|
||||
as.setRightLegPose(new EulerAngle(a, 0, 0));
|
||||
as.setLeftLegPose(new EulerAngle(-a, 0, 0));
|
||||
as.setRightArmPose(new EulerAngle(-a * 0.8, 0, 0));
|
||||
as.setLeftArmPose(new EulerAngle(a * 0.8, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private void idle(Elf el, boolean wave) {
|
||||
if (el.entity instanceof ArmorStand as) {
|
||||
as.setRightLegPose(EulerAngle.ZERO);
|
||||
as.setLeftLegPose(EulerAngle.ZERO);
|
||||
as.setRightArmPose(EulerAngle.ZERO);
|
||||
as.setLeftArmPose(EulerAngle.ZERO);
|
||||
}
|
||||
if (wave) {
|
||||
try { el.entity.swingMainHand(); } catch (Throwable ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
Collection<Player> audience() { return audience; }
|
||||
}
|
||||
@@ -114,55 +114,69 @@ public final class SantaEvent {
|
||||
|
||||
/** 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", "");
|
||||
return createFigure(loc, name, skin, skinPlayer, Color.RED, false);
|
||||
}
|
||||
|
||||
/** Erzeugt eine Figur (Santa, Wichtel ...): Spielermodell wenn moeglich, sonst Ruestungsstaender. */
|
||||
LivingEntity createFigure(Location loc, String name, String skin, String skinPlayer, Color color, boolean small) {
|
||||
LivingEntity figure = null;
|
||||
String model = mgr.cfg().getString("santa.model", "AUTO").toUpperCase();
|
||||
boolean mannequin = !model.equals("ARMOR_STAND") && MannequinSupport.supported();
|
||||
if (mannequin) {
|
||||
try {
|
||||
stand = MannequinSupport.spawn(loc, name, skin, skinPlayer);
|
||||
figure = 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));
|
||||
// Ohne eigenen Skin: gefaerbte Lederruestung
|
||||
var eq = figure.getEquipment();
|
||||
eq.setHelmet(dyed(Material.LEATHER_HELMET, color));
|
||||
eq.setChestplate(dyed(Material.LEATHER_CHESTPLATE, color));
|
||||
eq.setLeggings(dyed(Material.LEATHER_LEGGINGS, color));
|
||||
eq.setBoots(dyed(Material.LEATHER_BOOTS, color));
|
||||
}
|
||||
if (small) {
|
||||
try {
|
||||
var attr = org.bukkit.Registry.ATTRIBUTE.get(org.bukkit.NamespacedKey.minecraft("scale"));
|
||||
if (attr != null && figure.getAttribute(attr) != null) figure.getAttribute(attr).setBaseValue(0.6);
|
||||
} catch (Throwable ignored) { }
|
||||
}
|
||||
} 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 (figure != null) figure.remove();
|
||||
figure = null;
|
||||
}
|
||||
}
|
||||
if (stand == null) {
|
||||
stand = loc.getWorld().spawn(loc, ArmorStand.class, s -> {
|
||||
if (figure == null) {
|
||||
figure = loc.getWorld().spawn(loc, ArmorStand.class, s -> {
|
||||
s.setPersistent(false);
|
||||
s.setInvulnerable(true);
|
||||
s.setGravity(false);
|
||||
s.setArms(true);
|
||||
s.setSmall(small);
|
||||
s.setCustomName(name);
|
||||
s.setCustomNameVisible(true);
|
||||
ItemStack head = skin.isBlank() ? dyed(Material.LEATHER_HELMET) : skull(skin);
|
||||
ItemStack head = skin.isBlank() ? dyed(Material.LEATHER_HELMET, color) : 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));
|
||||
s.getEquipment().setChestplate(dyed(Material.LEATHER_CHESTPLATE, color));
|
||||
s.getEquipment().setLeggings(dyed(Material.LEATHER_LEGGINGS, color));
|
||||
s.getEquipment().setBoots(dyed(Material.LEATHER_BOOTS, color));
|
||||
});
|
||||
}
|
||||
return stand;
|
||||
return figure;
|
||||
}
|
||||
|
||||
private String barTitle() {
|
||||
return mgr.raw("santa-bossbar").replace("%time%", String.valueOf(left));
|
||||
}
|
||||
|
||||
private static ItemStack dyed(Material m) {
|
||||
private static ItemStack dyed(Material m) { return dyed(m, Color.RED); }
|
||||
|
||||
private static ItemStack dyed(Material m, Color c) {
|
||||
ItemStack it = new ItemStack(m);
|
||||
LeatherArmorMeta meta = (LeatherArmorMeta) it.getItemMeta();
|
||||
if (meta != null) { meta.setColor(Color.RED); it.setItemMeta(meta); }
|
||||
if (meta != null) { meta.setColor(c); it.setItemMeta(meta); }
|
||||
return it;
|
||||
}
|
||||
|
||||
@@ -189,6 +203,33 @@ public final class SantaEvent {
|
||||
private int walkPause;
|
||||
private float walkYaw;
|
||||
private Block lastStep;
|
||||
private double stepPhase;
|
||||
|
||||
/** Beine und Arme schwingen beim Laufen (Ruestungsstaender per Pose, Spielermodell macht es selbst). */
|
||||
private void animateLegs(double speed) {
|
||||
if (!mgr.cfg().getBoolean("santa.walk-animation", true)) return;
|
||||
stepPhase += 0.35 + speed * 3;
|
||||
if (stand instanceof ArmorStand as) {
|
||||
double a = Math.sin(stepPhase) * 0.7;
|
||||
as.setRightLegPose(new org.bukkit.util.EulerAngle(a, 0, 0));
|
||||
as.setLeftLegPose(new org.bukkit.util.EulerAngle(-a, 0, 0));
|
||||
as.setRightArmPose(new org.bukkit.util.EulerAngle(-a * 0.8, 0, 0));
|
||||
as.setLeftArmPose(new org.bukkit.util.EulerAngle(a * 0.8, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
/** Beim Stehenbleiben: Beine gerade, hin und wieder winkt Santa. */
|
||||
private void idle() {
|
||||
if (stand instanceof ArmorStand as) {
|
||||
as.setRightLegPose(org.bukkit.util.EulerAngle.ZERO);
|
||||
as.setLeftLegPose(org.bukkit.util.EulerAngle.ZERO);
|
||||
as.setRightArmPose(org.bukkit.util.EulerAngle.ZERO);
|
||||
as.setLeftArmPose(org.bukkit.util.EulerAngle.ZERO);
|
||||
}
|
||||
if (mgr.cfg().getBoolean("santa.wave", true)) {
|
||||
try { stand.swingMainHand(); } catch (Throwable ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
private void startWalking() {
|
||||
stopWalking();
|
||||
@@ -221,7 +262,11 @@ public final class SantaEvent {
|
||||
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; }
|
||||
if (walkPause > 0) {
|
||||
walkPause--;
|
||||
if (walkPause == 0 || walkPause % 30 == 15) idle(); // stehen: Beine ruhig, ab und zu winken
|
||||
return;
|
||||
}
|
||||
World w = loc.getWorld();
|
||||
|
||||
if (walkTarget == null) {
|
||||
@@ -263,6 +308,7 @@ public final class SantaEvent {
|
||||
Location next = new Location(w, nx, ny, nz, walkYaw, 0f);
|
||||
stand.teleport(next);
|
||||
loc = next;
|
||||
animateLegs(speed);
|
||||
|
||||
// Schritte: Geraeusch und kurze Schneespur
|
||||
if (stand.getTicksLived() % 9 == 0) w.playSound(next, Sound.BLOCK_SNOW_STEP, 0.6f, 1f);
|
||||
@@ -283,7 +329,7 @@ public final class SantaEvent {
|
||||
while (it.hasNext()) {
|
||||
var e = it.next();
|
||||
if (all || e.getValue() <= now) {
|
||||
if (e.getKey().getType() == Material.SNOW) e.getKey().setType(Material.AIR, false);
|
||||
ChristmasManager.removeSnowLayer(e.getKey());
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,39 +7,62 @@ 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). */
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Spielt Weihnachtsmelodien mit Glocken (Notenblock-Klaenge, keine Dateien noetig). */
|
||||
final class SantaMelody {
|
||||
// Melodie: {Halbtoene ueber C4 (-1 = Pause), Dauer in Viertelschlag-Achteln (1 = Achtel)}
|
||||
// Melodie: {Halbtoene ueber C4, Dauer in 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},
|
||||
};
|
||||
// "We Wish You a Merry Christmas"
|
||||
private static final int[][] WISH = {
|
||||
{2, 2}, {7, 2}, {7, 1}, {9, 1}, {7, 1}, {6, 1}, {4, 2}, {4, 2}, {4, 2}, {9, 2}, {9, 1}, {11, 1}, {9, 1}, {7, 1},
|
||||
{6, 2}, {2, 2}, {2, 2}, {11, 2}, {11, 1}, {12, 1}, {11, 1}, {9, 1}, {7, 2}, {4, 2}, {2, 1}, {2, 1}, {4, 2},
|
||||
{9, 2}, {6, 2}, {7, 6},
|
||||
};
|
||||
static final int[][][] TUNES = {JINGLE, WISH};
|
||||
|
||||
private final ChristmasManager mgr;
|
||||
private BukkitTask task;
|
||||
|
||||
SantaMelody(ChristmasManager mgr) { this.mgr = mgr; }
|
||||
|
||||
boolean playing() { return task != null; }
|
||||
|
||||
/** Santa-Event: Melodie fuer die Spieler der Welt (oder alle, santa.melody.everyone). */
|
||||
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 float volume = (float) mgr.cfg().getDouble("santa.melody.volume", 1.0);
|
||||
final int tempo = Math.max(1, mgr.cfg().getInt("santa.melody.tempo", 3));
|
||||
play(() -> everyone ? Bukkit.getOnlinePlayers() : world.getPlayers(), 0, volume, tempo, null);
|
||||
}
|
||||
|
||||
/** Spielt eine Melodie fuer ein sich aenderndes Publikum; done wird nach dem letzten Ton aufgerufen. */
|
||||
void play(Supplier<Collection<? extends Player>> audience, int tune, float volume, int tempo, Runnable done) {
|
||||
stop();
|
||||
final int[][] notes = TUNES[Math.floorMod(tune, TUNES.length)];
|
||||
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]++];
|
||||
if (pos[0] >= notes.length) {
|
||||
stop();
|
||||
if (done != null) done.run();
|
||||
return;
|
||||
}
|
||||
int[] n = notes[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()) {
|
||||
for (Player p : audience.get()) {
|
||||
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;
|
||||
wait[0] = n[1] * tempo - 1;
|
||||
}, 10L, 1L);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public final class SnowmanManager {
|
||||
void stop() {
|
||||
if (task != null) task.cancel();
|
||||
task = null;
|
||||
for (Block b : trail) if (b.getType() == Material.SNOW) b.setType(Material.AIR, false);
|
||||
for (Block b : trail) ChristmasManager.removeSnowLayer(b);
|
||||
trail.clear();
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public final class SnowmanManager {
|
||||
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);
|
||||
if (trail.remove(b)) ChristmasManager.removeSnowLayer(b);
|
||||
}, ticks);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ snow:
|
||||
# Ticks zwischen zwei Partikel-Wellen (höher = weniger Last)
|
||||
display-task-interval: 2
|
||||
default-on: true
|
||||
# Blacklist: in diesen Welten fällt kein Schnee (auch per /xmas snow world add <Welt>)
|
||||
disable-in-worlds: []
|
||||
# Nur in diesen WorldGuard-Regionen (nur wenn WorldGuard installiert ist). Leer = überall.
|
||||
enable-only-in-regions: false
|
||||
@@ -86,8 +87,11 @@ santa:
|
||||
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
|
||||
# Lauftempo in Blöcken pro Tick (0.12 = zügiger Spaziergang, 0.2 = Spielertempo)
|
||||
walk-speed: 0.12
|
||||
# Beine und Arme schwingen beim Laufen, Santa winkt beim Stehenbleiben
|
||||
walk-animation: true
|
||||
wave: true
|
||||
# Anflug mit Schlitten und zwei Rentieren, bevor Santa landet
|
||||
sleigh:
|
||||
enable: true
|
||||
@@ -150,6 +154,29 @@ magic-tree:
|
||||
# 0.2 (dezent) bis 5.0 (sehr viel). Standard 2.0
|
||||
intensity: 2.0
|
||||
max-presents-per-tree: 5
|
||||
# Ab welcher Stufe Geschenke entstehen: SAPLING, SMALL_TREE, TREE oder MAGIC_TREE
|
||||
# (darunter gibt es keine; ab dort gilt gift-cooldown der Stufe, bei -1 der Wert unten)
|
||||
presents-from-level: SMALL_TREE
|
||||
default-gift-cooldown: 300
|
||||
# Schmuck am Baum: heads = Deko-Köpfe, blocks = die alten Blöcke (Beton, Gold, Seelaterne)
|
||||
ornaments:
|
||||
mode: heads
|
||||
# Wie viel Schmuck am Baum hängt: Stück pro Ring (1-12) und nur jede n-te Ebene.
|
||||
# Weniger = dezenter. Gilt für neu gebaute Bäume bzw. nach dem nächsten Wachstum.
|
||||
per-ring: 3
|
||||
every-nth-layer: 2
|
||||
# Texturen der Deko-Köpfe: URL von textures.minecraft.net oder der "Value" (Base64)
|
||||
# von minecraft-heads.com. Eigene Köpfe (Christbaumkugeln, Sterne ...) einfach eintragen.
|
||||
heads:
|
||||
- "http://textures.minecraft.net/texture/33ba9a948ef125aafdb6bc91be6c804d71aee164ba2197b0b225a847fff048c0"
|
||||
- "http://textures.minecraft.net/texture/8c8a824b9b94939af2e8ce188482618dad933ae5dd671a7b9ac999d3a3b61a5"
|
||||
- "http://textures.minecraft.net/texture/83a2768de2af52b27c7bc1416dd535197b65158422673da9d3b0e61d49f33662"
|
||||
- "http://textures.minecraft.net/texture/70cd83be2b45225497325d958543465fd56bd65cd94a97e8fdfb1c1ec93b08f"
|
||||
- "http://textures.minecraft.net/texture/908b772ecaee706235d1adbf0b9a29b58ba9c0ed60de6f10efb1a7f86d9e"
|
||||
- "http://textures.minecraft.net/texture/e467e5795a6d4dcfd9f298c480117a761efbfa43a913ffec825bd07660faeff4"
|
||||
- "http://textures.minecraft.net/texture/845f39318890a8abfee5b320b35b959fc66e125e8f8cf3b7066d24f65de4b9"
|
||||
- "http://textures.minecraft.net/texture/ea2d228a34b93c51fb374fbb9543dabb40b9969e101b267f8a6a36ba7096c5d9"
|
||||
- "http://textures.minecraft.net/texture/f8cbd74cea8120322e72f55c39d0b5a8f3af31d5b883c30afdcacd1f592f801e"
|
||||
# Geschenk-Köpfe (Texturen-URLs von textures.minecraft.net)
|
||||
presents:
|
||||
- "http://textures.minecraft.net/texture/21bc9d42b0041e8f95cb9b26628fdaf50cd0e36f7bb9d6b3a4d2af3949da97d6"
|
||||
@@ -171,7 +198,8 @@ magic-tree:
|
||||
- SPONGE
|
||||
- DIAMOND_HOE
|
||||
- NAME_TAG
|
||||
# gift-cooldown in Sekunden (-1 = keine Geschenke), lvlup = Items, die zum nächsten Level nötig sind
|
||||
# gift-cooldown in Sekunden, lvlup = Items, die zum nächsten Level nötig sind
|
||||
# (Stufen unter presents-from-level geben keine Geschenke, egal was hier steht)
|
||||
levels:
|
||||
sapling:
|
||||
gift-cooldown: -1
|
||||
@@ -241,6 +269,31 @@ cookies:
|
||||
- "&7Warm und süß."
|
||||
- "&7Gibt Absorption."
|
||||
effects: ["absorption:60:1"]
|
||||
# Getränke vom Glühwein-Stand: Dampf-Partikel beim Trinken (steam: true)
|
||||
gluehwein:
|
||||
material: HONEY_BOTTLE
|
||||
name: "&5Glühwein"
|
||||
lore:
|
||||
- "&7Heiß und würzig."
|
||||
- "&7Wärmt und regeneriert."
|
||||
effects: ["regeneration:8:1", "fire_resistance:60:0"]
|
||||
steam: true
|
||||
kinderpunsch:
|
||||
material: HONEY_BOTTLE
|
||||
name: "&dKinderpunsch"
|
||||
lore:
|
||||
- "&7Fruchtig und warm."
|
||||
- "&7Gibt kurz Tempo."
|
||||
effects: ["speed:40:0", "jump_boost:40:0"]
|
||||
steam: true
|
||||
kakao:
|
||||
material: HONEY_BOTTLE
|
||||
name: "&6Heiße Schokolade"
|
||||
lore:
|
||||
- "&7Süß und cremig."
|
||||
- "&7Gibt Absorption."
|
||||
effects: ["absorption:60:1"]
|
||||
steam: true
|
||||
|
||||
# --- Wichteln ----------------------------------------------------------------
|
||||
wichteln:
|
||||
@@ -381,7 +434,54 @@ wishes:
|
||||
# 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:
|
||||
# Bereich des Marktes rund um den Marktpunkt (/xmas market set), in Blöcken
|
||||
radius: 45
|
||||
# Schnee fällt nur im Markt (gilt nur, wenn ein Marktpunkt gesetzt ist)
|
||||
snow-only-in-market: true
|
||||
# Titel mit Glockenklang beim Betreten
|
||||
welcome:
|
||||
enable: true
|
||||
# Leise Weihnachtsmusik im Markt (Glocken), dazwischen Pausen
|
||||
music:
|
||||
enable: true
|
||||
volume: 0.35
|
||||
# Ticks pro Achtelnote (kleiner = schneller)
|
||||
tempo: 3
|
||||
pause-seconds: 40
|
||||
# Santa auf dem Markt: aufstellen mit /xmas market santa set (an der Stelle, an der du stehst).
|
||||
# Rechtsklick gibt jedem Spieler einmal pro Tag ein Geschenk.
|
||||
santa:
|
||||
enable: true
|
||||
name: "&c&lSanta Claus"
|
||||
rewards:
|
||||
- { item: "COOKIE:4" }
|
||||
- { item: "cookie:lebkuchen:1" }
|
||||
- { item: "GOLD_NUGGET:3" }
|
||||
# Wichtel laufen zwischen den Ständen herum (nur, solange Spieler im Markt sind)
|
||||
elves:
|
||||
enable: true
|
||||
count: 4
|
||||
# Umkreis um den Marktpunkt, in dem sie laufen
|
||||
radius: 20
|
||||
# Tempo in Blöcken pro Tick
|
||||
speed: 0.1
|
||||
# Eigener Skin (URL von textures.minecraft.net), leer = grüne Lederrüstung
|
||||
skin-url: ""
|
||||
skin-player: ""
|
||||
names:
|
||||
- "&aWichtel Fridolin"
|
||||
- "&aWichtel Pip"
|
||||
- "&aWichtel Tinka"
|
||||
- "&aWichtel Knut"
|
||||
- "&aWichtel Lotta"
|
||||
stalls:
|
||||
gluehwein:
|
||||
name: "&5Glühwein-Stand"
|
||||
profession: FARMER
|
||||
trades:
|
||||
- { give: "cookie:gluehwein:1", price: ["EMERALD:2"] }
|
||||
- { give: "cookie:kinderpunsch:1", price: ["EMERALD:1"] }
|
||||
- { give: "cookie:kakao:1", price: ["COCOA_BEANS:4", "SUGAR:1"] }
|
||||
plaetzchen:
|
||||
name: "&6Plätzchen-Stand"
|
||||
profession: BUTCHER
|
||||
|
||||
@@ -24,7 +24,7 @@ messages:
|
||||
- "&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 market spawn <id> | remove | list | santa <set|remove|reset> &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"
|
||||
@@ -50,6 +50,11 @@ messages:
|
||||
# Schnee
|
||||
snow-on: "&aSchnee ist für dich &leingeschaltet&a."
|
||||
snow-off: "&7Schnee ist für dich &causgeschaltet&7."
|
||||
snow-world-list: "&7Welten ohne Schnee: &e%worlds%"
|
||||
snow-world-usage: "&cNutzung: /christmas snow world <add|remove|list> [Welt]"
|
||||
snow-world-unknown: "&cDie Welt &e%world% &cgibt es nicht."
|
||||
snow-world-added: "&aIn der Welt &e%world% &afällt jetzt kein Schnee mehr."
|
||||
snow-world-removed: "&aIn der Welt &e%world% &afällt wieder Schnee."
|
||||
snow-disabled-global: "&cSchnee ist auf dem Server deaktiviert."
|
||||
|
||||
# Schneemänner
|
||||
@@ -248,6 +253,14 @@ messages:
|
||||
|
||||
# Marktstände
|
||||
market-usage: "&cNutzung: /xmas market spawn <id> | remove | list"
|
||||
market-welcome-title: "&c&lWeihnachtsmarkt"
|
||||
market-welcome-subtitle: "&7Willkommen! Besuche Santa für dein Tagesgeschenk"
|
||||
market-santa-set: "&aSanta steht jetzt hier auf dem Markt. Spieler bekommen per Rechtsklick einmal pro Tag ein Geschenk."
|
||||
market-santa-removed: "&7Markt-Santa entfernt."
|
||||
market-santa-reset: "&7Die Tagesgeschenke von Santa sind für alle zurückgesetzt."
|
||||
market-santa-hint: "&6Rechtsklick auf Santa für dein Tagesgeschenk"
|
||||
market-santa-gift: "&aHo ho ho! Santa hat ein Geschenk für dich. Komm morgen wieder!"
|
||||
market-santa-claimed: "&7Ho ho ho! Das Geschenk für heute hast du schon. Komm morgen wieder!"
|
||||
market-unknown: "&cUnbekannter Stand: %id%"
|
||||
market-spawned: "&aMarktstand aufgestellt."
|
||||
market-removed: "&7Marktstand entfernt."
|
||||
|
||||
@@ -25,7 +25,7 @@ messages:
|
||||
- "&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 market spawn <id> | remove | list | santa <set|remove|reset> &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"
|
||||
@@ -53,6 +53,11 @@ messages:
|
||||
# Snow
|
||||
snow-on: "&aSnow is now &eenabled &afor you."
|
||||
snow-off: "&7Snow is now &cdisabled &7for you."
|
||||
snow-world-list: "&7Worlds without snow: &e%worlds%"
|
||||
snow-world-usage: "&cUsage: /christmas snow world <add|remove|list> [world]"
|
||||
snow-world-unknown: "&cThe world &e%world% &cdoes not exist."
|
||||
snow-world-added: "&aSnow no longer falls in the world &e%world%&a."
|
||||
snow-world-removed: "&aSnow falls again in the world &e%world%&a."
|
||||
snow-disabled-global: "&cSnow is disabled on this server."
|
||||
|
||||
# Snowmen
|
||||
@@ -249,6 +254,14 @@ messages:
|
||||
|
||||
# Market stalls
|
||||
market-usage: "&cUsage: /xmas market spawn <id> | remove | list"
|
||||
market-welcome-title: "&c&lChristmas Market"
|
||||
market-welcome-subtitle: "&7Welcome! Visit Santa for your daily gift"
|
||||
market-santa-set: "&aSanta now stands here at the market. Players get one gift per day with a right-click."
|
||||
market-santa-removed: "&7Market Santa removed."
|
||||
market-santa-reset: "&7Santa's daily gifts have been reset for everyone."
|
||||
market-santa-hint: "&6Right-click Santa for your daily gift"
|
||||
market-santa-gift: "&aHo ho ho! Santa has a gift for you. Come back tomorrow!"
|
||||
market-santa-claimed: "&7Ho ho ho! You already got today's gift. Come back tomorrow!"
|
||||
market-unknown: "&cUnknown stall: %id%"
|
||||
market-spawned: "&aMarket stall placed."
|
||||
market-removed: "&7Market stall removed."
|
||||
|
||||
Reference in New Issue
Block a user