2 Commits
Author SHA1 Message Date
M_Viper 069be7c33b Upload via GUI (53 Dateien) 2026-11-13 17:46:28 +00:00
M_Viper 950c1fbf47 Upload via GUI (53 Dateien) 2026-11-13 17:46:26 +00:00
13 changed files with 1003 additions and 124 deletions
+17 -1
View File
@@ -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);
}
+103 -3
View File
@@ -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
+14 -1
View File
@@ -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."
+14 -1
View File
@@ -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."