Lua-Scripting: Hooks & Lebenszyklus
Diese Seite behandelt jeden Hook, den eine Lua-Power definieren kann, die Reihenfolge der Hook-Ausführung, wie jeder Boss seine eigene isolierte Laufzeitumgebung erhält und welche Standardbibliotheksfunktionen innerhalb der Sandbox verfügbar sind.
Wenn du noch keine Lua-Power geschrieben hast, beginne zuerst mit Erste Schritte.
Diese Seite dokumentiert Hooks für Boss-Lua-Powers in plugins/EliteMobs/powers/. NPC-Lua-Scripts verwenden ihren eigenen Ordner plugins/EliteMobs/npc_scripts/ sowie NPC-spezifische Hooks wie on_npc_interact und on_npc_proximity_enter; siehe NPC-Scripts.
Hook-Referenz
Jede Lua-Power-Datei gibt eine Tabelle zurück. Jeder Key in dieser Tabelle (außer api_version und priority) muss einer der unten aufgeführten Hooks sein. Die Laufzeitumgebung ruft die passende Funktion auf, wann immer das entsprechende Spielereignis ausgelöst wird.
| Hook | Wird ausgelöst, wenn | context.player verfügbar? |
|---|---|---|
on_spawn | Der Elite-Mob spawnt | Nein |
on_game_tick | Einmal pro Server-Tick bei aktiver, nicht pausierter Runtime-Clock; bei 20 TPS normalerweise alle 50 ms | Nein |
on_boss_damaged | Der Boss erleidet Schaden aus einer beliebigen Quelle | Nein |
on_boss_damaged_by_player | Der Boss erleidet Schaden von einem Spieler | Ja |
on_reinforcement_damaged_by_player | Eine von diesem Boss beschworene Verstärkung erleidet Spielerschaden; der Hook läuft auf den Powers des Beschwörers | Ja |
on_boss_damaged_by_elite | Der Boss erleidet Schaden von einem anderen Elite-Mob | Nein |
on_player_damaged_by_boss | Ein Spieler erleidet Schaden von diesem Boss | Ja |
on_enter_combat | Der Boss tritt in den Kampf ein | Ja |
on_exit_combat | Der Boss verlässt den Kampf | Nein |
on_heal | Der Boss heilt sich | Nein |
on_boss_target_changed | EliteMobs meldet ein Spielerziel-Ereignis, auch die erste Zielerfassung; nicht jeden Zielwechsel oder Zielverlust | Ja |
on_death | Der Boss stirbt | Nein |
on_phase_switch | Ein Phasen-Boss wechselt in eine neue Phase | Nein |
on_mind_action | Ein nativer Mind fordert eine Aktion seiner Fähigkeiten an; siehe Aktionskontext | Nein |
on_zone_enter | Eine Entität betritt eine überwachte Zone | Ja (wenn die Entität ein Spieler ist) |
on_zone_leave | Eine Entität verlässt eine überwachte Zone | Ja (wenn die Entität ein Spieler ist) |
Wenn bei context.player „Nein“ steht, gibt der Zugriff darauf nil zurück. Prüfe vor der Verwendung immer auf nil.
on_boss_damaged und die spezifischen Schadens-Hooks verwenden getrennte Ereignispfade. Ein Treffer kann beide auslösen; sie schließen einander nicht aus. Der Ziel-Hook lässt sich über diese Lua-Ereignistabelle nicht abbrechen und meldet kein vorheriges Ziel.
on_zone_enter / on_zone_leave nicht ausDer Loader akzeptiert und validiert beide Hooks, aber ein Lua-Power allein kann sie nicht auslösen. Nur der Zonen-Listener von EliteScript sendet diese Ereignisse. Er startet, wenn ein YAML-eliteScript: auf demselben Boss ZoneEnterEvent oder ZoneLeaveEvent unter Events: aufführt. Ein Boss mit ausschließlich Lua-Powers lädt solche Hooks ohne Fehler, ruft sie jedoch nie auf.
Lua-Watcher arbeiten getrennt. context.zones:watch_zone(...) und context.script:zone(...):watch(...) rufen die übergebenen on_enter-/on_leave-Callbacks direkt auf. Verwende diese Watcher, sofern du die Lua-Power nicht bewusst mit einem YAML-Zonenskript kombinierst.
YAML-Zonenereignisse erreichen alle passenden Powers des Bosses. Sie enthalten die eintretende oder austretende Entität, aber keine Kennung der ursprünglichen Zone oder des Skripts. Mehrere YAML-Listener können daher mehrere Ereignisse für dieselbe Entität senden.
NPC-Lua-Skripte verhalten sich anders. Ihr context.zones stammt aus MagmaCore, dessen watch tatsächlich on_zone_enter / on_zone_leave auslöst. Siehe NPC-Scripts.
Typische Multi-Hook-Power
Eine einzelne Lua-Power kann so viele Hooks definieren, wie sie benötigt. Unten ist ein Grundgerüst, das drei Hooks gemeinsam verwendet:
return {
api_version = 1,
on_enter_combat = function(context)
-- Initialize per-fight state when combat begins
context.state.hit_count = 0
context.log:info("Combat started!")
end,
on_boss_damaged_by_player = function(context)
-- Track hits and trigger an ability every 5th hit
context.state.hit_count = (context.state.hit_count or 0) + 1
if context.state.hit_count % 5 ~= 0 then
return
end
if not context.cooldowns:check_local("counter_attack", 100) then
return
end
-- Fire a projectile back at the player
local origin = context.boss:get_location()
origin:add(0, 1, 0)
context.boss:summon_projectile(
"SMALL_FIREBALL", origin, context.player:get_location(), 1.5
)
end,
on_death = function(context)
-- Spawn an explosion particle on death
context.world:spawn_particle_at_location(
context.boss:get_location(), "EXPLOSION_EMITTER", 1
)
end
}
Event-Daten (context.event)
Einige Hooks erhalten eine context.event-Tabelle, die Daten über das Spielereignis bereitstellt, das den Hook ausgelöst hat. Die verfügbaren Felder hängen davon ab, welcher Hook ausgeführt wird.
Schadens-Hooks
Gilt für on_boss_damaged, on_boss_damaged_by_player, on_reinforcement_damaged_by_player, on_boss_damaged_by_elite und on_player_damaged_by_boss.
| Feld / Methode | Typ | Beschreibung |
|---|---|---|
event.damage_amount | double | Schadenswert beim Erstellen dieser Ereignistabelle |
event.get_damage_amount() | double | Liest den aktuellen Schaden nach vorherigen Änderungen |
event.damage_cause | string | Spigot-DamageCause-Name (z. B. "ENTITY_ATTACK", "PROJECTILE") |
event.damager | entity-Tabelle | Ursprünglicher Bukkit-Verursacher bei Schaden durch eine Entität, auch in on_boss_damaged. Bei Projektiltreffern ist dies das Projektil, nicht der Schütze. |
event.projectile | entity-Tabelle | Die Projektil-Entität, falls der Verursacher ein Projektil war. |
event.set_damage_amount(n) | — | Überschreibt den Schaden mit einem festen Wert |
event.multiply_damage_amount(n) | — | Multipliziert den aktuellen Schaden mit n |
event.cancel_event() | — | Bricht das Schadensereignis vollständig ab |
on_boss_damaged_by_player = function(context)
-- Halve all projectile damage
if context.event.damage_cause == "PROJECTILE" then
context.event.multiply_damage_amount(0.5)
end
end
Rufe event.set_damage_amount(n) und event.multiply_damage_amount(n) mit einem Punkt auf. Beide Setter lesen direkt das erste Argument und entfernen kein durch Doppelpunkt übergebenes self. event:transfer_damage(target, amount) unterstützt dagegen Doppelpunkt-Aufrufe.
Bei Spielerschaden an Eliten bezeichnet event.entity die beschädigte Elite. In on_reinforcement_damaged_by_player bleibt context.boss der Beschwörer und context.player der angreifende Spieler. Der Hook erhält das ursprüngliche, veränderbare Schadensereignis der Verstärkung.
Diese Hooks bieten auch event.is_damage_transfer und event:transfer_damage(target, amount). Das Ziel muss eine andere lebende Elite sein, die der besitzende Boss beeinflussen darf. Der Betrag muss endlich und nicht negativ sein. Ein abgebrochenes Ereignis, Betrag null oder eine verschachtelte Übertragung liefert false. Ein gültiger Aufruf löst einen getrennten, dem Spieler zugeschriebenen Treffer aus und liefert true. Er zieht nichts vom ursprünglichen Schaden ab und garantiert nicht, dass andere Listener den neuen Treffer zulassen. Verwende event.set_damage_amount(...), um Schaden aufzuteilen statt zu duplizieren. Übertragener Schaden kann nicht rekursiv weiterübertragen werden.
Spawn-Hook
Gilt für on_spawn.
| Feld / Methode | Typ | Beschreibung |
|---|---|---|
event.spawn_reason | string | Spigot-SpawnReason-Name |
event.cancel_event() | — | Bricht den Spawn ab |
Death-Hook
Gilt für on_death.
| Feld / Methode | Typ | Beschreibung |
|---|---|---|
event.entity | entity-Tabelle | Die sterbende Entität |
Zonen-Hooks
Gilt für on_zone_enter und on_zone_leave.
| Feld / Methode | Typ | Beschreibung |
|---|---|---|
event.entity | entity-Tabelle | Die Entität, die die Zone betritt oder verlässt |
Von Lua erstellte Zonen-Watcher befüllen context.event nicht; sie übergeben die betretende/verlassende Entität direkt an ihren Callback.
Abbrechbare Events (Allgemein)
Hooks mit abbrechbarem Spielereignis bieten event.cancel_event(). Bei on_game_tick, on_mind_action und normalen Scheduler-Callbacks ist context.event gleich nil. Hooks wie on_heal besitzen ein Ereignis, aber diese Tabelle bietet keine besonderen Heilungsfelder. Prüfe vor Aufrufen die dokumentierten Mitglieder.
Die Boss-Ereignistabelle bietet nicht cancel(), uncancel() oder is_cancelled des gemeinsamen Wrappers. Das Abbrechen stoppt die Weitergabe an spätere Powers desselben Bosses, beendet aber nicht deine aktuelle Lua-Funktion. Ergänze return, falls deren restlicher Code nicht mehr laufen soll.
Eine vollständige Übersicht über die Felder der entity-Tabelle findest du unter Boss & Entitäten. Werte für Schadensursachen und Spawn-Gründe findest du unter Enums & Werte.
Reihenfolge der Hook-Ausführung
Der Ereignisverteiler des Bosses besucht angehängte Skript-Powers in Ausführungsreihenfolge. Das Feld priority einer Lua-Power bestimmt ihre Position:
- Niedrigere Werte werden zuerst ausgeführt (Standard ist
0). - Gleiche Prioritäten behalten die Einfügereihenfolge der Boss-Powers bei. Verlass dich nicht auf eine feste Reihenfolge zwischen verschiedenen Ladewegen.
return {
api_version = 1,
priority = -10, -- runs before most other powers
on_boss_damaged_by_player = function(context)
-- Record this power's last attacker before its later hooks run
context.state.last_attacker = context.player.uuid
end
}
YAML-EliteScript-Powers nutzen denselben Verteiler mit Priorität 0. Negative Lua-Prioritäten laufen davor, positive danach. Dies ordnet ein Ereignis auf einem Boss, nicht getrennte Bukkit-Ereignisse oder geplante Tasks. Bereits abgebrochene Ereignisse stoppen den Verteiler; deshalb erhält nicht zwangsläufig jede Power das Ereignis. Nach den eigenen Powers einer Verstärkung erhält ihr direkter Beschwörer den Verstärkungsschadens-Hook nur, wenn das Ereignis weiterhin nicht abgebrochen ist.
Laufzeitmodell
Eine Laufzeitumgebung pro Power und Boss
Jede angehängte Lua-Power erhält für jeden Boss eine eigene Laufzeitumgebung. Beim Start wertet EliteMobs den geladenen Quellcode in einer neuen Sandbox aus und speichert die zurückgegebene Tabelle. Beim Despawn oder Entfernen des Bosses wird die Laufzeit beendet. Ein Boss mit zwei Lua-Powers hat daher zwei unabhängige Zustandstabellen und Umgebungen.
Das bedeutet:
- Dateilokale Variablen und Funktionen sowie selbst definierte Lua-Globals sind privat für die Laufzeit dieser Power auf diesem Boss.
- Die Hook-Funktionen der zurückgegebenen Tabelle werden niemals zwischen Bossen geteilt.
Der Loader wertet die Datei außerdem einmal aus, um ihre Rückgabetabelle zu validieren. Initialisierung auf Dateiebene darf keinen Hook-Kontext voraussetzen. Eine reine Tick-Power benötigt kein on_spawn, um ihre Clock zu starten. Laufzeitinitialisierung und Spawn-Callback sind getrennte Schritte.
State-Isolation
Jede Laufzeit besitzt eine eigene context.state-Tabelle, getrennt von anderen Powers desselben Bosses und derselben Power auf anderen Bossen. Speichere darin Zähler, Flags, Timer und andere Power-Daten zwischen Hooks. Der Zustand besteht während der aktiven Laufzeit; er wird nicht über Despawns oder Neustarts hinweg auf der Festplatte gespeichert.
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
-- Each boss tracks its own enrage counter independently
context.state.enrage_hits = (context.state.enrage_hits or 0) + 1
if context.state.enrage_hits >= 20 then
context.boss:apply_potion_effect("SPEED", 200, 2)
end
end
}
Eigentümerschaft geplanter Tasks
Alle über context.scheduler erstellten Tasks gehören der Laufzeitumgebung, die sie erstellt hat. Wenn ein Boss despawnt:
- Die Laufzeitumgebung ruft
shutdown()auf. - Jeder eigene Task – sowohl einmalige (
run_after) als auch wiederholende (run_every) – wird automatisch abgebrochen. - Alle Zonen-Watches werden geleert.
- Änderungen an KI, Wahrnehmung, Skalierung und Unverwundbarkeit, die diese Power noch hält, werden wiederhergestellt, eine von ihr gesetzte Heilung wird entfernt, und ihr Controller für zielverfolgende Feuerbälle stoppt.
Die Laufzeit räumt ihre eigenen Scheduler-Tasks beim Entfernen des Bosses auf. Manche Java-Spielmechaniken starten getrennte Tasks mit eigener Lebensdauer; lies die jeweilige Dokumentation. Brich wiederholende Tasks ab, sobald sie im normalen Spiel nicht mehr benötigt werden:
return {
api_version = 1,
on_enter_combat = function(context)
local pulse_count = 0
local task_id
task_id = context.scheduler:run_every(20, function(tick_context)
pulse_count = pulse_count + 1
if pulse_count > 10 or not tick_context.boss.exists then
tick_context.scheduler:cancel_task(task_id)
return
end
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "FLAME", amount = 20, speed = 0.1 }
)
end)
end
}
Verhalten der Per-Tick-Clock
Die interne Tick-Clock einer Lua-Power-Instanz läuft nur, wenn die Power einen on_game_tick-Hook definiert. Zonen-Watches, die über context.zones:watch_zone(...) oder context.script:zone(...):watch(...) erstellt werden, erzeugen ihre eigenen wiederholenden Tasks, anstatt den Top-Level-on_game_tick-Hook der Power zu aktivieren.
Ohne on_game_tick und Zonen-Watcher registriert die Laufzeit diese Tick-Clock nicht. Explizite Scheduler-Tasks und Tasks von Spielmechaniken laufen weiter nach ihren eigenen Zeitplänen. Tick-Arbeit und eigene Zonen-Watcher werden beim Despawn oder Schließen der Laufzeit abgebrochen.
Pausieren erhält Lua-Zustand und eigene Tasks, überspringt aber deren Arbeit. Der Boss-Ereignispfad prüft außerdem, ob Powers unterdrückt sind. Entfernen des Bosses oder Schließen der Laufzeit beendet die eigene Arbeit, statt sie nur zu pausieren.
Verhalten bei Fehlern und Performance
EliteMobs erzwingt strenge Fehler- und Performance-Limits für Lua-Powers:
Ausnahmen (Exceptions)
Wenn eine Hook-Funktion oder ein geplanter Callback einen Lua-Fehler auslöst (oder eine Java-Exception aus einem API-Aufruf auftaucht), wird die Power für diese Boss-Instanz sofort deaktiviert. Die Laufzeitumgebung wird heruntergefahren und alle eigenen Tasks werden abgebrochen.
Die fehlerhafte Instanz bleibt geschlossen; spätere Schadens-Hooks erzeugen sie nicht stillschweigend neu. Korrigiere und lade die Definition neu und erstelle eine neue Laufzeit. Bewusstes Abbauen durch den Besitzer kann die alte Referenz für eine spätere Wiederherstellung des Körpers löschen.
Der Fehler wird mit dem Dateinamen der Power und dem ausgeführten Hook protokolliert. Eine Zeilennummer erscheint, wenn sie verfügbar ist:
[Lua] Error in 'frost_cone.lua' at line 35 during 'on_boss_damaged_by_player':
[Lua] -> ...explanation of what went wrong...
[Lua] -> Script has been disabled for this entity to prevent further errors.
Ausführungsbudget
Jeder Hook, geplante Callback und jede anfängliche Dateiauswertung hat ein Budget von 250.000 Lua-Instruktionen und 50 ms Thread-CPU-Zeit. Falls die JVM die CPU-Zeit nicht messen kann, gilt stattdessen eine Grenze von 250 ms verstrichener Zeit. Die VM prüft die Zeit alle 1024 Instruktionen. Verschachtelte Aufrufe teilen sich das Budget. Der Fehler nennt die überschrittene Grenze:
Lua instruction budget exceeded (250000 instruction limit)
Lua CPU-time budget exceeded (50ms current-thread CPU limit)
Lua elapsed-time fallback budget exceeded (250ms fallback; current-thread CPU time unavailable)
Die VM unterbricht unkontrollierte Lua-Schleifen während der Ausführung. Laufende Java-Aufrufe kann sie nicht unterbrechen. Nach der Rückkehr gibt es keine zusätzliche 50-ms-Grenze für die verstrichene Zeit. Aufwendige Weltoperationen bleiben daher problematisch. Um innerhalb des Budgets zu bleiben:
- Vermeide unbegrenzte Schleifen innerhalb von Hooks. Verwende
context.scheduler:run_every(...), um die Arbeit über mehrere Ticks zu verteilen. - Halte
on_game_tick-Handler leichtgewichtig – sie werden bei jedem einzelnen Tick ausgeführt. - Verlagere aufwendige Initialisierung in
on_spawnoderon_enter_combat, anstatt sie bei jedem Tick zu wiederholen. - Partikelschleifen über große Zonen können schnell 250.000 Instruktionen verbrauchen. Verwende
coverage, um weniger Positionen zu verarbeiten.
Lua-Sandbox
Lua-Powers laufen innerhalb einer gesandboxten LuaJ-Umgebung. Mehrere Globals, die auf das Dateisystem oder die Java-Laufzeitumgebung zugreifen könnten, sind entfernt.
Entfernte Globals
Die folgenden Standard-Lua-Globals sind auf nil gesetzt und können nicht verwendet werden:
| Entfernt | Warum |
|---|---|
debug | Stellt internen VM-Zustand bereit |
dofile | Dateisystemzugriff |
io | Dateisystemzugriff |
load | Laden beliebigen Codes |
loadfile | Dateisystemzugriff |
luajava | Direkter Zugriff auf Java-Klassen |
module | Modulsystem (nicht benötigt) |
os | Zugriff auf das Betriebssystem |
package | Modulsystem (nicht benötigt) |
require | Modulsystem / Dateisystemzugriff |
Die Debug-Hooks der VM bleiben intern installiert, um Instruktionen für das Ausführungsbudget zu zählen. Die Tabelle debug wird unmittelbar danach auf nil gesetzt, sodass Skripte nicht darauf zugreifen können.
Verfügbare Standardbibliothek
Die Sandbox behält die Standardbibliotheken von LuaJ einschließlich dieser häufig verwendeten Funktionen:
| Kategorie | Funktionen |
|---|---|
| Math | math.abs, math.ceil, math.floor, math.max, math.min, math.random, math.sin, math.cos, math.sqrt, math.pi und alle anderen math.*-Funktionen |
| String | string.byte, string.char, string.find, string.format, string.gsub, string.len, string.lower, string.match, string.rep, string.sub, string.upper und alle anderen string.*-Funktionen |
| Table | table.insert, table.remove, table.sort, table.concat und alle anderen table.*-Funktionen |
| Iteratoren | pairs, ipairs, next |
| Typ | type, tostring, tonumber, select, unpack |
| Fehlerbehandlung | pcall, xpcall, error, assert |
| Sonstiges | print, rawget, rawset, rawequal, rawlen, setmetatable, getmetatable |
Strings teilen sich eine gesperrte Metatable. getmetatable("") gibt false zurück, und Methodenaufrufe auf Strings wie ("text"):upper() verwenden immer die Standard-String-Funktionen, selbst wenn ein Skript Funktionen in seiner eigenen string-Tabelle ergänzt oder ersetzt.
print schreibt in die Server-Konsole. Verwende bevorzugt context.log:info(msg) oder context.log:warn(msg). Boss-Logs enthalten den Dateinamen der Power nicht automatisch. Ergänze ihn in der Nachricht, wenn mehrere Powers gleichzeitig protokollieren können.
em-Helfer-Namespace
Die em-Tabelle ist zum Ladezeitpunkt der Datei verfügbar (bevor irgendein Hook läuft). Sie stellt Helfer-Konstruktoren zum Erstellen von location-Tabellen, Vektor-Tabellen und Zonendefinitionen bereit, die in der gesamten API verwendet werden.
| Funktion | Zweck |
|---|---|
em.create_location(x, y, z [, world, yaw, pitch]) | Erstellt eine location-Tabelle mit optionalem Weltnamen, Yaw und Pitch |
em.create_vector(x, y, z) | Erstellt eine Vektor-Tabelle |
em.zone.create_sphere_zone(radius) | Erstellt eine Kugel-Zonendefinition |
em.zone.create_dome_zone(radius) | Erstellt eine Kuppel-Zonendefinition |
em.zone.create_cylinder_zone(radius, height) | Erstellt eine Zylinder-Zonendefinition |
em.zone.create_cuboid_zone(x, y, z) | Erstellt eine Quader-Zonendefinition |
em.zone.create_cone_zone(length, radius) | Erstellt eine Kegel-Zonendefinition |
em.zone.create_static_ray_zone(length, thickness) | Erstellt eine Static-Ray-Zonendefinition |
em.zone.create_rotating_ray_zone(length, point_radius, animation_duration) | Erstellt eine Rotating-Ray-Zonendefinition |
em.zone.create_translating_ray_zone(length, point_radius, animation_duration) | Erstellt eine Translating-Ray-Zonendefinition |
em enthält außerdem em.location mit gemeinsamen schreibgeschützten Weltabfragen: is_in_dungeon, is_protected, owned_by, owners, kinds_at und has_kind. Das erste Argument ist eine Positionstabelle. Fehlende Positionen, Werte ohne Tabellenform sowie fehlende oder ungeladene Welten liefern false oder eine leere Tabelle. Fehlende Koordinaten werden null; ungültige Koordinatentypen können einen Fehler auslösen. Gib beim Erstellen solcher Positionen die Welt ausdrücklich an. Signaturen und Rückgabeformate stehen in der Lua-API-Referenz.
-- Skip a destructive mechanic inside claims or protected regions
if em.location.is_protected(context.player:get_location()) then return end
Es gibt keine Lua-Bindung für Baurechte-Prüfungen. em.location.is_protected zeigt, ob irgendein Schutz-Plugin die Position beansprucht; es ermittelt keine spielerspezifischen Baurechte.
Zonen-Builder geben verkettbare Tabellen mit :set_center(loc) (oder :set_origin(loc) / :set_destination(loc), je nach Zonentyp) zurück. Diese sind dafür gedacht, am Anfang einer Datei oder innerhalb von Hooks verwendet zu werden:
-- At file scope: create a reusable zone shape
local blast_zone = em.zone.create_sphere_zone(5)
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
-- Anchor the zone to the boss's current location at call time
blast_zone:set_center(context.boss:get_location())
local entities = context.zones:get_entities_in_zone(blast_zone)
for i = 1, #entities do
if entities[i].is_player then
entities[i]:apply_potion_effect("SLOWNESS", 60, 1)
end
end
end
}
Eine vollständige Aufschlüsselung von Zonenformen, Filtern, Watchern und Targeting-Mustern findest du unter Zonen & Targeting.
Nächste Schritte
- Boss & Entitäten –
context.boss,context.player, Entitäts-Wrapper - Welt & Umgebung – Partikel, Sounds, Spawning,
context.world - Zonen & Targeting – native Zonen, Script-Utilities,
context.zones/context.script - Beispiele & Muster – vollständige funktionierende Powers, die du studieren und anpassen kannst
- Enums & Werte – Spigot-Javadoc-Links für alle String-Konstanten
