Zum Hauptinhalt springen

Lua-Scripting: Hooks & Lebenszyklus

webapp_banner.jpg

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 Sie noch keine Lua-Power geschrieben haben, beginnen Sie zuerst mit Erste Schritte.

Boss-Power-Hooks

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.

HookWird ausgelöst, wenncontext.player verfügbar?
on_spawnDer Elite-Mob spawntNein
on_game_tickEinmal pro Server-Tick (50 ms), solange die Runtime-Clock aktiv istNein
on_boss_damagedDer Boss erleidet Schaden aus einer beliebigen QuelleNein
on_boss_damaged_by_playerDer Boss erleidet Schaden von einem SpielerJa
on_boss_damaged_by_eliteDer Boss erleidet Schaden von einem anderen Elite-MobNein
on_player_damaged_by_bossEin Spieler erleidet Schaden von diesem BossJa
on_enter_combatDer Boss tritt in den Kampf einJa
on_exit_combatDer Boss verlässt den KampfNein
on_healDer Boss heilt sichNein
on_boss_target_changedDer Boss wechselt sein ZielJa
on_deathDer Boss stirbtNein
on_phase_switchEin Phasen-Boss wechselt in eine neue PhaseNein
on_zone_enterEine Entität betritt eine überwachte ZoneJa (wenn die Entität ein Spieler ist)
on_zone_leaveEine Entität verlässt eine überwachte ZoneJa (wenn die Entität ein Spieler ist)

Wenn bei context.player „Nein“ steht, gibt der Zugriff darauf nil zurück. Prüfen Sie vor der Verwendung immer auf nil.

Quelle der Zonen-Hooks

Die Top-Level-Hooks on_zone_enter und on_zone_leave werden von EliteScript-/ScriptZone-Events ausgelöst. Von Lua erstellte Watcher aus context.zones:watch_zone(...) und context.script:zone(...):watch(...) rufen ihre on_enter-/on_leave-Callbacks direkt auf, anstatt diese Top-Level-Hooks zu verwenden.

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 a firework 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_boss_damaged_by_elite und on_player_damaged_by_boss.

Feld / MethodeTypBeschreibung
event.damage_amountdoubleRoher Schadenswert
event.damage_causestringSpigot-DamageCause-Name (z. B. "ENTITY_ATTACK", "PROJECTILE")
event.damagerentity-TabelleEntität, die den Schaden verursacht hat. Nur bei Schaden-durch-Entität-Hooks vorhanden.
event.projectileentity-TabelleDie 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

Spawn-Hook

Gilt für on_spawn.

Feld / MethodeTypBeschreibung
event.spawn_reasonstringSpigot-SpawnReason-Name
event.cancel_event()Bricht den Spawn ab

Death-Hook

Gilt für on_death.

Feld / MethodeTypBeschreibung
event.entityentity-TabelleDie sterbende Entität

Zonen-Hooks

Gilt für on_zone_enter und on_zone_leave.

Feld / MethodeTypBeschreibung
event.entityentity-TabelleDie 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)

Jeder Hook, dessen zugrunde liegendes Spielereignis abbrechbar ist, stellt event.cancel_event() bereit. Wenn context.event für einen bestimmten Hook nil ist (z. B. on_game_tick, on_heal), gibt es kein zugrunde liegendes Ereignis, mit dem interagiert werden kann.

Eine vollständige Übersicht über die Felder der entity-Tabelle finden Sie unter Boss & Entitäten. Werte für Schadensursachen und Spawn-Gründe finden Sie unter Enums & Werte.


Reihenfolge der Hook-Ausführung

Wenn an einem Boss mehrere Lua-Powers angehängt sind, wird der Hook jeder Power für dasselbe Event aufgerufen. Die Reihenfolge wird durch das Feld priority bestimmt:

  • Niedrigere Werte werden zuerst ausgeführt (Standard ist 0).
  • Powers mit derselben Priorität werden in der Ladereihenfolge ausgeführt (praktisch nicht festgelegt).
return {
api_version = 1,
priority = -10, -- runs before most other powers

on_boss_damaged_by_player = function(context)
-- This runs early, so other powers see any state changes we make
context.state.last_attacker = context.player.uuid
end
}

Die Priorität beeinflusst nur die Reihenfolge unter den Lua-Powers desselben Bosses. Sie interagiert nicht mit der Ausführungsreihenfolge von EliteScript.


Laufzeitmodell

Eine Laufzeitumgebung pro Boss

Jede Boss-Entität erhält ihre eigene unabhängige Lua-Laufzeitinstanz. Wenn der Boss spawnt, lädt EliteMobs den Lua-Quellcode, wertet ihn in einer frischen Sandbox-Umgebung aus und speichert die zurückgegebene Tabelle. Wenn der Boss despawnt oder entfernt wird, wird die Laufzeitumgebung heruntergefahren.

Das bedeutet:

  • Globale Lua-Variablen, die während der Dateiauswertung gesetzt werden (wie Hilfsfunktionen mit local function), sind für diesen Boss privat.
  • Die Hook-Funktionen der zurückgegebenen Tabelle werden niemals zwischen Bossen geteilt.

State-Isolation

Jede Laufzeitumgebung hat ihre eigene context.state-Tabelle. Der State eines Bosses ist für jeden anderen Boss vollständig unsichtbar, selbst wenn sie sich dieselbe Lua-Power-Datei teilen. Verwenden Sie context.state, um Zähler, Flags, Timer oder beliebige boss-spezifische Daten zu speichern, die Sie über Hooks hinweg benötigen.

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:

  1. Die Laufzeitumgebung ruft shutdown() auf.
  2. Jeder eigene Task – sowohl einmalige (run_after) als auch wiederholende (run_every) – wird automatisch abgebrochen.
  3. Alle Zonen-Watches werden geleert.

Sie müssen geplante Tasks bei der Entfernung eines Bosses niemals manuell bereinigen. Sie sollten wiederholende Tasks während des normalen Spielablaufs jedoch dennoch abbrechen, sobald sie nicht mehr benötigt werden, um unnötige Arbeit zu vermeiden:

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.

Wenn eine Power weder on_game_tick noch Zonen-Watcher hat, fällt keine Per-Tick-Arbeit an. Tick-Arbeit und Zonen-Watcher-Tasks werden automatisch abgebrochen, wenn der Boss despawnt oder die Laufzeitumgebung heruntergefahren wird.


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.

Der Fehler wird zusammen mit dem Dateinamen der Power, der Zeilennummer und dem gerade ausgeführten Hook in der Server-Konsole protokolliert:

[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

Jede Hook-Aufruf und jeder Callback-Aufruf wird gemessen. Wenn ein einzelner Aufruf länger als 50 Millisekunden dauert, wird die Power mit einer Konsolenwarnung deaktiviert:

[Lua] my_power.lua took 73ms in 'on_game_tick' (limit: 50ms) — script disabled to prevent lag.

Dies verhindert, dass außer Kontrolle geratene Scripts den Server einfrieren. Um innerhalb des Budgets zu bleiben:

  • Vermeiden Sie unbegrenzte Schleifen innerhalb von Hooks. Verwenden Sie context.scheduler:run_every(...), um die Arbeit über mehrere Ticks zu verteilen.
  • Halten Sie on_game_tick-Handler leichtgewichtig – sie werden bei jedem einzelnen Tick ausgeführt.
  • Verlagern Sie aufwendige Initialisierung in on_spawn oder on_enter_combat, anstatt sie bei jedem Tick zu wiederholen.

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:

EntferntWarum
debugStellt internen VM-Zustand bereit
dofileDateisystemzugriff
ioDateisystemzugriff
loadLaden beliebigen Codes
loadfileDateisystemzugriff
luajavaDirekter Zugriff auf Java-Klassen
moduleModulsystem (nicht benötigt)
osZugriff auf das Betriebssystem
packageModulsystem (nicht benötigt)
requireModulsystem / Dateisystemzugriff

Verfügbare Standardbibliothek

Alles andere aus der Lua-Standardbibliothek funktioniert normal:

KategorieFunktionen
Mathmath.abs, math.ceil, math.floor, math.max, math.min, math.random, math.sin, math.cos, math.sqrt, math.pi und alle anderen math.*-Funktionen
Stringstring.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
Tabletable.insert, table.remove, table.sort, table.concat und alle anderen table.*-Funktionen
Iteratorenpairs, ipairs, next
Typtype, tostring, tonumber, select, unpack
Fehlerbehandlungpcall, xpcall, error, assert
Sonstigesprint, rawget, rawset, rawequal, rawlen, setmetatable, getmetatable
Tipp

print schreibt in die Server-Konsole, aber bevorzugen Sie für Ausgaben context.log:info(msg) oder context.log:warn(msg). Diese werden mit dem Power-Namen versehen, was es einfacher macht nachzuvollziehen, welche Power die Nachricht erzeugt hat.


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.

FunktionZweck
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

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].type == "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 finden Sie unter Zonen & Targeting.


Nächste Schritte