Zum Hauptinhalt springen

Lua-Scripting: Beispiele & Muster

Diese Seite enthält vollständige funktionierende Beispiele von EliteMobs Lua-Fähigkeiten sowie praktische Muster, Best Practices und Tipps. Jedes Beispiel enthält eine Erklärung, was es tut und warum.

Wenn Sie neu bei Lua-Fähigkeiten sind, beginnen Sie mit Erste Schritte. Für vollständige API-Details siehe die API-Referenz, Boss & Entitäten, Welt & Umgebung, Zonen & Zielerfassung und Enums.

webapp_banner.jpg


Beispiel: Zonenbasierte Zielerfassung mit Script-Utilities

Was dieses Beispiel lehrt: Wie man context.script verwendet, um EliteScript-artige Zonengeometrie aus Lua zu erstellen, Partikel zu spawnen und Entitäten in der Zone Schaden zuzufügen.

Vollständige Power-Datei (klicken zum Aufklappen)
return {
api_version = 1,

on_boss_damaged_by_player = function(context)
local cone = context.script:zone({
shape = "CONE",
Target = {
targetType = "SELF",
offset = "0,1,0"
},
Target2 = {
targetType = "NEARBY_PLAYERS",
range = 20
},
radius = 5
})

context.script:spawn_particles(
cone:full_target(0.4),
{
particle = "FLAME",
amount = 1,
speed = 0.05
}
)

context.script:damage(cone:full_target(), 1.0, 1.5)
end
}

Erklärung

  1. Zonenerstellung -- context.script:zone(...) erstellt eine Kegelform unter Verwendung derselben Feldnamen wie EliteScript-Zonen. Target setzt den Kegelursprung (der Boss selbst, um 1 Block nach oben versetzt), und Target2 setzt das Ziel (die nächsten Spieler innerhalb von 20 Blöcken). radius steuert, wie breit sich der Kegel öffnet.

  2. Partikel-Spawning -- cone:full_target(0.4) gibt ein Ziel-Handle zurück, das zu allen Positionen innerhalb des Kegels mit 40% Abdeckung aufgelöst wird (bei jedem Aufruf werden zufällig 40% der Zonenpunkte abgetastet). Die Partikelspezifikation verwendet dieselben Feldnamen wie EliteScript-Partikel: particle, amount und speed.

  3. Schaden -- context.script:damage(cone:full_target(), 1.0, 1.5) trifft alle lebenden Entitäten im gesamten Kegel. Die erste Zahl (1.0) ist der Basisschaden, und die zweite (1.5) ist der auf Spieler angewendete Schadensmultiplikator.

EliteScript-Feldnamen

Die an context.script übergebenen Zonen- und Zieltabellen verwenden EliteScript-Feldnamen (targetType, shape, Target, Target2, range, offset, coverage). Die vollständige Liste finden Sie unter EliteScript-Zonen und EliteScript-Ziele.


Beispiel: Zustand + Scheduler-Angriffsschleife

Was dieses Beispiel lehrt: Verwendung von context.state zur Verfolgung des Laufzeitzustands, context.scheduler für wiederholende Aufgaben und ordnungsgemäßen Kampfstart/Ende-Lebenszyklus.

Vollständige Power-Datei (klicken zum Aufklappen)
local function pick_action(context)
local roll = math.random(1, 2)

if roll == 1 then
context.boss:play_model_animation("slam")
else
context.boss:play_model_animation("roar")
end
end

return {
api_version = 1,

on_spawn = function(context)
context.state.started = false
context.state.loop_task_id = nil
end,

on_enter_combat = function(context)
if context.state.started then
return
end

context.state.started = true

context.state.loop_task_id = context.scheduler:run_every(100, function(loop_context)
if loop_context.boss.exists then
pick_action(loop_context)
end
end)
end,

on_exit_combat = function(context)
if context.state.loop_task_id ~= nil then
context.scheduler:cancel_task(context.state.loop_task_id)
context.state.loop_task_id = nil
end
context.state.started = false
end
}

Erklärung

  1. Zustandsinitialisierung -- on_spawn setzt context.state.started auf false und context.state.loop_task_id auf nil. Die state-Tabelle bleibt für die gesamte Lebensdauer dieser Boss-Instanz bestehen, sodass hier gesetzte Werte über Hooks hinweg erhalten bleiben.

  2. Kampfschutz -- on_enter_combat prüft context.state.started, bevor die Schleife gestartet wird. Dies verhindert mehrere überlappende Schleifen, wenn das Ereignis mehr als einmal ausgelöst wird.

  3. Scheduler-Muster -- context.scheduler:run_every(100, callback) führt den Callback alle 100 Ticks (5 Sekunden) aus. Der Callback erhält einen frischen Kontext, sodass loop_context.boss den neuesten Boss-Snapshot liefert. Der Scheduler gibt eine numerische Task-ID zurück, die Sie im State speichern.

  4. Aufräumen beim Verlassen -- on_exit_combat bricht die wiederholende Aufgabe mit der gespeicherten Task-ID ab und setzt den State zurück. Dies ist kritisch: Ohne Aufräumen läuft der Scheduler auch nach Kampfende weiter.

Scheduler-Aufgaben immer abbrechen

Wenn Sie eine wiederholende Aufgabe in on_enter_combat starten, brechen Sie sie immer in on_exit_combat ab. Das Vergessen des Abbrechens hinterlässt eine Hintergrundaufgabe, die bis zum Despawn des Bosses läuft, was Leistung verschwendet und unerwartetes Verhalten verursachen kann.


Beispiel: Feuereffekt bei Treffer

Was dieses Beispiel lehrt: Ein einfacher „bei Treffer Effekt anwenden"-Power -- das häufigste Muster für Kampffähigkeiten.

Vollständige Power-Datei (klicken zum Aufklappen)
return {
api_version = 1,

on_player_damaged_by_boss = function(context)
-- Guard: the player may be nil in edge cases
if context.player == nil then
return
end

-- Check and set a 60-tick (3 second) local cooldown in one call
if not context.cooldowns:check_local("fire_touch", 60) then
return
end

-- Set the player on fire for 60 ticks (3 seconds)
context.player:set_fire_ticks(60)

-- Visual feedback: spawn flame particles at the player's location
context.world:spawn_particle_at_location(
context.player.current_location,
{ particle = "FLAME", amount = 20, speed = 0.1 }
)

-- Tell the player what happened
context.player:send_message("&cThe boss's touch burns!")

-- Set the global power cooldown so other powers on this boss
-- don't all fire at the same instant
context.cooldowns:set_global(40)
end
}

Erklärung

  1. Nil-Schutz -- context.player ist ein Lazy Key, der zum am Ereignis beteiligten Spieler aufgelöst wird. In seltenen Grenzfällen (z.B. der Spieler hat sich zwischen Ereignisauslösung und Hook-Ausführung abgemeldet) kann er nil sein. Prüfen Sie immer, bevor Sie ihn verwenden.

  2. Lokaler Cooldown -- context.cooldowns:check_local("fire_touch", 60) macht zwei Dinge atomar: Es prüft, ob der Cooldown-Schlüssel "fire_touch" bereit ist, und wenn ja, setzt es den Cooldown sofort auf 60 Ticks. Wenn der Cooldown nicht bereit ist, gibt es false zurück und die Funktion beendet sich vorzeitig. Der Schlüssel "fire_touch" ist auf diese Boss-Instanz beschränkt -- andere Bosse mit derselben Fähigkeit haben unabhängige Cooldowns.

  3. Feuer-Ticks -- context.player:set_fire_ticks(60) setzt den Spieler für 60 Spielticks (3 Sekunden) in Brand. Dies ruft direkt die zugrunde liegende Bukkit-Methode auf.

  4. Partikel -- context.world:spawn_particle_at_location(location, spec) spawnt Partikel an einer bestimmten Position. Die Spec-Tabelle akzeptiert particle (Bukkit-Partikel-Enum-Name), amount und speed.

  5. Nachricht -- context.player:send_message(text) sendet eine farbcodierte Chat-Nachricht. Standard-Minecraft-Farbcodes wie &c (rot) funktionieren automatisch.

  6. Globaler Cooldown -- context.cooldowns:set_global(40) setzt alle Fähigkeiten dieses Bosses auf einen 40-Tick (2 Sekunden) Cooldown. Dies verhindert, dass mehrere Fähigkeiten gleichzeitig ausgelöst werden.


Beispiel: Zonenbasierte AoE-Fähigkeit mit nativen Lua-Zonen

Was dieses Beispiel lehrt: Erstellen und Abfragen nativer Lua-Zonen, um Spielern in einem Bereich Schaden zuzufügen.

Vollständige Power-Datei (klicken zum Aufklappen)
return {
api_version = 1,

on_spawn = function(context)
context.state.aoe_task_id = nil
end,

on_enter_combat = function(context)
-- Prevent duplicate loops
if context.state.aoe_task_id ~= nil then
return
end

context.state.aoe_task_id = context.scheduler:run_every(60, function(tick_context)
-- Make sure the boss is still alive
if not tick_context.boss.exists then
return
end

-- Check a local cooldown so this doesn't stack with other effects
if not tick_context.cooldowns:check_local("pulse_aoe", 60) then
return
end

-- Build a sphere zone centered on the boss's current position
local zone_def = {
kind = "sphere",
radius = 8,
origin = tick_context.boss:get_location()
}

-- Find all players inside the sphere
local victims = tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })

-- Damage and show particles on each player found
for i = 1, #victims do
local victim = victims[i]
victim:deal_custom_damage(4.0)

tick_context.world:spawn_particle_at_location(
victim.current_location,
{ particle = "DUST", amount = 15, speed = 0, red = 128, green = 0, blue = 255 }
)
end

-- Spawn visual ring particles at the boss
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "SPELL_MOB", amount = 40, speed = 0.1 }
)

-- Set global cooldown
tick_context.cooldowns:set_global(60)
end)
end,

on_exit_combat = function(context)
if context.state.aoe_task_id ~= nil then
context.scheduler:cancel_task(context.state.aoe_task_id)
context.state.aoe_task_id = nil
end
end
}

Erklärung

  1. Zustandseinrichtung -- on_spawn initialisiert aoe_task_id mit nil. Diese Task-ID wird die Referenz auf die wiederholende Scheduler-Aufgabe halten.

  2. Wiederholender Angriff -- on_enter_combat startet eine wiederholende Aufgabe alle 60 Ticks (3 Sekunden). Der Schutz am Anfang verhindert das Starten einer zweiten Schleife, wenn der Kampf erneut betreten wird.

  3. Zonendefinition -- Die zone_def-Tabelle verwendet die native Lua-Zonensyntax. Das kind-Feld gibt die Form an ("sphere"), radius setzt die Größe, und origin wird auf die aktuelle Position des Bosses zum Zeitpunkt des Callback-Aufrufs gesetzt. Das bedeutet, die Zone folgt dem Boss, wenn er sich bewegt.

  4. Entitätsabfrage -- tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" }) gibt ein Lua-Array aller Spielertabellen innerhalb der Kugel zurück. Die filter-Option akzeptiert "players", "elites", "mobs" oder "living" (Standard).

  5. Schaden verursachen -- victim:deal_custom_damage(4.0) verursacht 4 Schadenspunkte, die dem Boss zugeschrieben werden. Dies verwendet das benutzerdefinierte EliteMobs-Schadenssystem, das Rüstung und andere Kampfmodifikatoren berücksichtigt.

  6. Partikel-Feedback -- Lila Staubpartikel erscheinen auf jedem Opfer, und Umgebungspartikel erscheinen an der Position des Bosses, um den Puls visuell zu signalisieren.

  7. Aufräumen -- on_exit_combat bricht die wiederholende Aufgabe ab und bereinigt den State, dem gleichen Muster wie im vorherigen Beispiel folgend.

Native Zonen vs. Script-Utility-Zonen

Dieses Beispiel verwendet native Lua-Zonen (context.zones:get_entities_in_zone()), die eine einfache Tabelle mit kind, radius, origin usw. nehmen. Die Script-Utilities (context.script:zone(...)) verwenden EliteScript-Feldnamen wie shape, Target und Target2. Beide funktionieren -- verwenden Sie native Zonen für einfache Formen und context.script, wenn Sie EliteScripts erweiterte Zielauflösung benötigen.


Beispiel: Mehrstufige Boss-Mechanik

Was dieses Beispiel lehrt: Verwendung von State zur Verfolgung von Boss-Phasen und Verhaltensänderung bei Lebensschwellenwerten.

Vollständige Power-Datei (klicken zum Aufklappen)
local function phase_one_attack(context)
-- Slow, heavy slam
context.boss:play_model_animation("slam")

local zone_def = {
kind = "sphere",
radius = 5,
origin = context.boss:get_location()
}

local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(3.0)
end

context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "EXPLOSION", amount = 3, speed = 0 }
)
end

local function phase_two_attack(context)
-- Fast, frantic multi-hit
context.boss:play_model_animation("frenzy")

local zone_def = {
kind = "sphere",
radius = 8,
origin = context.boss:get_location()
}

local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(2.0)
targets[i]:apply_potion_effect("SLOWNESS", 40, 1)
end

context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "DUST", amount = 30, speed = 0.2, red = 255, green = 0, blue = 0 }
)

context.world:play_sound_at_location(
context.boss:get_location(),
"entity.wither.ambient",
1.0, 1.5
)
end

return {
api_version = 1,

on_spawn = function(context)
context.state.phase = 1
context.state.attack_task_id = nil
context.state.phase_switched = false
end,

on_enter_combat = function(context)
if context.state.attack_task_id ~= nil then
return
end

-- Start the phase 1 attack loop: every 100 ticks (5 seconds)
context.state.attack_task_id = context.scheduler:run_every(100, function(tick_context)
if not tick_context.boss.exists then
return
end

if not tick_context.cooldowns:check_local("phase_attack", 100) then
return
end

phase_one_attack(tick_context)
end)
end,

on_game_tick = function(context)
-- Only check phase transition every 20 ticks (1 second) to stay lightweight
if not context.cooldowns:check_local("phase_check", 20) then
return
end

-- Skip if already in phase 2
if context.state.phase ~= 1 then
return
end

-- Check health ratio
local health_ratio = context.boss.health / context.boss.maximum_health

if health_ratio <= 0.5 then
-- Transition to phase 2
context.state.phase = 2
context.state.phase_switched = true

context.log:info("Boss entering phase 2 at " .. tostring(math.floor(health_ratio * 100)) .. "% health")

-- Cancel the old attack loop
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end

-- Play transition effects
context.boss:play_model_animation("transform")

-- Announce the phase change to nearby players
local nearby = context.players.nearby_players(40)
for i = 1, #nearby do
nearby[i]:send_message("&4&lThe boss enters a frenzy!")
nearby[i]:show_title("&4Phase 2", "&cThe boss is enraged!", 10, 40, 10)
end

-- Start a faster phase 2 attack loop: every 40 ticks (2 seconds)
context.state.attack_task_id = context.scheduler:run_every(40, function(tick_context)
if not tick_context.boss.exists then
return
end

if not tick_context.cooldowns:check_local("phase_attack", 40) then
return
end

phase_two_attack(tick_context)
end)
end
end,

on_exit_combat = function(context)
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end
end
}

Erklärung

  1. Zustandsinitialisierung -- on_spawn setzt phase auf 1, attack_task_id auf nil und phase_switched auf false. Diese Werte bleiben über alle Hooks für diese Boss-Instanz bestehen.

  2. Phase-1-Angriffsschleife -- on_enter_combat startet eine wiederholende Aufgabe, die phase_one_attack alle 100 Ticks ausführt. Der Angriff erstellt eine 5-Block-Kugel, schadet Spielern darin und spielt eine Slam-Animation mit Explosionspartikeln ab.

  3. Phasenprüfung in on_game_tick -- on_game_tick wird jeden einzelnen Server-Tick ausgelöst, daher prüft es zuerst einen 20-Tick-Cooldown ("phase_check"), um aufwendige Logik nicht jeden Tick auszuführen. Wenn der Boss bereits in Phase 2 ist, wird vorzeitig beendet.

  4. Lebensschwellenwert -- context.boss.health / context.boss.maximum_health gibt die aktuelle Gesundheit als Bruch an. Wenn sie auf 50% oder darunter fällt, beginnt der Übergang.

  5. Phasenübergang -- Die alte Angriffsschleife wird abgebrochen, eine Transformationsanimation wird abgespielt, nahegelegene Spieler erhalten eine Nachricht und einen Titel, und eine neue schnellere Angriffsschleife startet (alle 40 Ticks statt 100).

  6. Phase-2-Angriffe -- phase_two_attack verwendet eine größere Kugel (8 Blöcke), verursacht etwas weniger Schaden pro Treffer, feuert aber viel häufiger, wendet einen Langsamkeitseffekt an und verwendet rote Staubpartikel und einen Wither-Sound für ein anderes Gefühl.

  7. Protokollierung -- context.log:info(...) schreibt in die Serverkonsole, was für das Debugging von Phasenübergängen während der Entwicklung unschätzbar wertvoll ist.

  8. Aufräumen -- on_exit_combat bricht die aktuell aktive Angriffsschleife ab, unabhängig davon, in welcher Phase sich der Boss befindet.

on_game_tick leichtgewichtig halten

on_game_tick wird jeden Server-Tick ausgeführt (20 Mal pro Sekunde). Schützen Sie aufwendige Arbeit immer mit einer Cooldown-Prüfung, wie mit check_local("phase_check", 20) gezeigt. Wenn Ihr Hook 50ms überschreitet, wird EliteMobs die Fähigkeit automatisch deaktivieren.


KI-Generierungstipps

Wenn Sie möchten, dass KI Lua-Fähigkeiten zuverlässig generiert, stellen Sie sicher, dass der Prompt Folgendes enthält:

  • Exakter Hook-Name -- z.B. on_player_damaged_by_boss, nicht „wenn der Boss einen Spieler trifft".
  • Native Lua-Zonen oder Script-Utilities -- geben Sie an, welche. Native Zonen verwenden context.zones:get_entities_in_zone(zone_def, opts) mit kind, radius, origin. Script-Utilities verwenden context.script:zone(...) mit EliteScript-Feldnamen wie shape, Target, Target2.
  • Lokale und globale Cooldowns -- geben Sie den Cooldown-Schlüsselnamen und die Dauer in Ticks an. context.cooldowns:check_local(key, ticks) für Boss-spezifische Cooldowns, context.cooldowns:set_global(ticks) für den gemeinsamen Power-Cooldown.
  • Benutzerdefinierte Modellanimationsnamen -- z.B. context.boss:play_model_animation("slam"). Die KI kann keine Animationsnamen erraten; stellen Sie sie bereit.
  • Zielauswahl -- geben Sie an, ob die Fähigkeit context.player (den Ereignisspieler), context.players.nearby_players(range) oder eine Zonenabfrage anvisiert.
  • Effekttyp -- Schaden (deal_custom_damage), Trank (apply_potion_effect), Feuer (set_fire_ticks), Geschwindigkeit (set_velocity_vector, apply_push_vector), etc.
  • Nur dokumentierte Methodennamen verwenden -- wenn es nicht auf der API-Referenzseite steht, existiert es nicht.
  • Script-Utility-Specs verwenden EliteScript-Feldnamen -- targetType, shape, Target, Target2, range, offset, coverage, nicht Lua-artige Namen.
  • Scheduler-Callbacks akzeptieren frischen Kontext -- der Callback-Parameter ist ein neuer Kontext, nicht der äußere. Verwenden Sie immer tick_context (oder wie auch immer Sie den Parameter benennen) innerhalb von Callbacks.

Gutes Prompt-Beispiel

Schreibe eine Lua-Fähigkeit, die on_enter_combat verwendet, um eine wiederholende Aufgabe alle 80 Ticks zu starten. Erstelle bei jedem Tick eine native Lua-Kugelzone (Radius 6) zentriert auf dem Boss, frage Spieler darin ab und verursache 2.0 benutzerdefinierten Schaden bei jedem. Verwende einen lokalen Cooldown-Schlüssel "pulse" mit Dauer 80. Breche die Aufgabe in on_exit_combat ab. Spawne DUST-Partikel (red=0, green=255, blue=100) bei jedem Opfer.

Zusätzliche Einschränkungen zum Einbeziehen

  • „Gib eine einzelne Tabelle mit api_version = 1 zurück."
  • „Initialisiere alle State-Felder in on_spawn."
  • „Schütze Scheduler-Callbacks mit if not tick_context.boss.exists then return end."
  • „Breche alle wiederholenden Aufgaben in on_exit_combat ab."
  • „Erfinde keine Methodennamen -- verwende nur Methoden von der API-Referenzseite."
  • „Verwende context.cooldowns:check_local(key, ticks) für kombiniertes Prüfen-und-Setzen."

QC-Checkliste für manuelle oder KI-Überprüfung

Verwenden Sie diese Checkliste, um eine Lua-Fähigkeit vor dem Einsatz zu überprüfen:

  1. Die Datei gibt genau eine Tabelle mit api_version = 1 zurück.
  2. Jeder Hook-Name stimmt exakt mit einem Eintrag in der Hook-Liste überein (z.B. on_player_damaged_by_boss, nicht on_player_hit).
  3. context.player wird mit == nil geschützt, bevor es in Hooks verwendet wird, wo es nil sein kann.
  4. context.state-Felder werden in on_spawn initialisiert.
  5. Jeder context.scheduler:run_every(...)-Aufruf hat ein passendes context.scheduler:cancel_task(...) in on_exit_combat.
  6. Scheduler-Callbacks verwenden den eigenen Kontextparameter des Callbacks, nicht den äußeren context.
  7. Cooldown-Schlüssel sind beschreibende Strings (z.B. "fire_pulse") und Dauern sind in Ticks.
  8. on_game_tick-Hooks schützen aufwendige Arbeit hinter einer Cooldown-Prüfung.
  9. Alle Methodennamen existieren in der API-Referenz -- keine erfundenen Aliase.
  10. Script-Utility-Tabellen verwenden EliteScript-Feldnamen (targetType, shape, Target, etc.), nicht Lua-artige Namen.
  11. Native Zonendefinitionen verwenden kind, radius, origin, destination, etc.
  12. Die Fähigkeit ruft keine blockierenden oder lang laufenden Operationen innerhalb eines Hooks oder Callbacks auf.
  13. Partikelspezifikationen verwenden gültige Bukkit-Partikel-Enum-Namen in UPPER_CASE (z.B. "FLAME", "DUST", "EXPLOSION").

Best Practices

  • Beginnen Sie mit einem kleinen Hook und verifizieren Sie. Schreiben Sie einen einzelnen on_spawn, der eine Log-Nachricht sendet. Bestätigen Sie, dass er auslöst. Dann bauen Sie von dort aus weiter.

  • Halten Sie Hilfsfunktionen lokal. Deklarieren Sie Helfer wie local function pick_action(context) über der Return-Tabelle. Dies hält sie aus dem globalen Scope und vermeidet Kollisionen mit anderen Lua-Fähigkeiten, die in derselben Laufzeit geladen werden.

  • Legen Sie Geometrie in Script-Utilities. Wenn Sie Kegel, rotierende Strahlen, translierende Strahlen oder animierte Zonen benötigen, verwenden Sie context.script:zone(...) mit EliteScript-Feldnamen. Die Script-Utilities verwenden die kampferprobte EliteScript-Zonenengine.

  • Verwenden Sie context.state für Laufzeitzustand. Verwenden Sie keine Lua-Globalvariablen. context.state ist auf eine einzelne Boss-Instanz beschränkt und bleibt über Hooks für die Lebensdauer dieses Bosses bestehen.

  • Verwenden Sie benannte lokale Cooldown-Schlüssel. Verwenden Sie anstelle von bloßen Zahlen beschreibende Schlüssel wie "fire_touch" oder "aoe_pulse". Dies erleichtert das Debugging und verhindert versehentliche Kollisionen zwischen verschiedenen Cooldowns in derselben Fähigkeit.

  • Halten Sie on_game_tick leicht. Schützen Sie es immer hinter einer Cooldown-Prüfung. Wenn Ihre Logik jeden Tick läuft, muss sie in deutlich unter 50ms abgeschlossen sein, oder die Fähigkeit wird deaktiviert.

  • Brechen Sie wiederholende Aufgaben ab, wenn sie fertig sind. Jedes run_every muss ein passendes cancel_task in on_exit_combat haben (und möglicherweise on_death). Durchgesickerte Aufgaben verschwenden CPU und können Null-Referenz-Fehler nach dem Despawn des Bosses verursachen.

  • Verwenden Sie frische Scheduler-Callback-Kontexte. Scheduler-Callbacks (run_every, run_after) erhalten einen frischen Kontext als Parameter. Verwenden Sie immer diesen Parameter -- nicht den äußeren context -- da der äußere Kontext veraltete Snapshots halten kann.

  • Protokollieren Sie Zustandsübergänge mit context.log:info(). Fügen Sie während der Entwicklung Protokollierung für Phasenwechsel, Cooldown-Starts und Scheduler-Starts/Stopps hinzu. Entfernen oder ändern Sie zu context.log:debug() vor dem Einsatz.

  • Verwenden Sie vorhandene EliteScript-Dokumentation wieder. Die Seiten Zonen, Ziele, Relative Vektoren und Bedingungen dokumentieren die gleichen Feldnamen, die die Script-Utilities akzeptieren. Duplizieren Sie diese Informationen nicht in Ihrer Lua-Fähigkeit -- verweisen Sie einfach darauf.


Häufige Anfängerfehler

  • Verwendung des äußeren context innerhalb eines Scheduler-Callbacks. Der äußere Kontext erfasst einen Snapshot zum Zeitpunkt der Hook-Ausführung. Verwenden Sie innerhalb eines run_every- oder run_after-Callbacks immer den eigenen Parameter des Callbacks (z.B. tick_context), der Ihnen einen frischen Snapshot gibt.

  • Vergessen, wiederholende Aufgaben abzubrechen. Wenn Sie ein run_every in on_enter_combat starten, aber nie abbrechen, läuft die Aufgabe bis der Boss vom Server entfernt wird, auch nach Kampfende.

  • State nicht in on_spawn initialisieren. Wenn Sie context.state.phase in on_game_tick lesen, aber nie in on_spawn setzen, wird es nil sein und Ihre Vergleiche werden sich unerwartet verhalten.

  • context.player ohne Nil-Schutz prüfen. In Hooks wie on_player_damaged_by_boss ist der Spieler fast immer verfügbar -- aber „fast immer" ist nicht „immer". Ein einziger fehlender Nil-Schutz kann die Fähigkeit zum Absturz bringen.

  • Lua-artige Feldnamen in Script-Utility-Aufrufen verwenden. Die Script-Utilities erwarten targetType, Target, Target2, shape -- nicht target_type, target, target2, zone_shape. Nicht übereinstimmende Namen erzeugen stillschweigend keine Ergebnisse.

  • Schwere Logik in on_game_tick ohne Cooldown-Gate ausführen. Dieser Hook wird jeden Server-Tick ausgelöst. Selbst einfache Arithmetik, die 20 Mal pro Sekunde über viele Bosse hinweg wiederholt wird, summiert sich.

  • Methodennamen erfinden. Wenn eine Methode nicht in der API-Referenz aufgeführt ist, existiert sie nicht. Häufige Fehler sind das Schreiben von entity:teleport(loc) statt entity:teleport_to_location(loc), oder player:set_velocity(vec) statt player:set_velocity_vector(vec).

  • context.boss.health zum Setzen der Gesundheit verwenden. context.boss.health ist ein schreibgeschützter Snapshot. Um den Boss zu heilen, verwenden Sie context.boss:restore_health(amount).

  • api_version = 1 vergessen. Die zurückgegebene Tabelle muss dieses Feld enthalten, oder EliteMobs wird die Fähigkeit nicht laden.


Nächste Schritte