Lua-Scripting: Beispiele & Muster
Diese Seite enthält vollständige Lua-Fähigkeiten für EliteMobs mit Erklärungen. Speichere jedes Beispiel in einer eigenen .lua-Datei, hänge es an einen Testboss, führe /em reload aus und spawne nach Änderungen einen neuen Boss. Animationsnamen wie slam, roar, frenzy und transform müssen auf dem eigenen Modell des Bosses vorhanden sein; sie sind nicht eingebaut. Ohne eigenes Modell überspringt der Boss diese Animationsaufrufe. Die Zeitangaben setzen 20 TPS voraus.
Wenn du neu bei Lua-Fähigkeiten bist, beginne mit Erste Schritte. Für NPC-Skripte siehe NPC-Skripte. Die vollständige API für Boss-Fähigkeiten steht unter API-Referenz, Boss & Entitäten, Welt & Umgebung, Zonen & Zielerfassung und Enums.
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
-
Zonenerstellung --
context.script:zone(...)verwendet die Felder der EliteScript-Zonen.Targetsetzt die Kegelspitze einen Block über den Boss. Die erste aufgelösteTarget2-Position bestimmt die Basismitte und damit die Kegellänge;radius = 5setzt den Basisradius.NEARBY_PLAYERSsucht in einer Box mit 20 Blöcken Ausdehnung je Achsenrichtung und garantiert nicht den nächsten Spieler. Verwende in diesem SchadenshookDIRECT_TARGET, um auf den Angreifer zu zielen. Ohne aufgelösten Spieler entsteht kein Kegel; die Endpunkte müssen verschieden sein und in derselben Welt liegen. -
Partikel-Spawning --
cone:full_target(0.4)liefert ein Ziel-Handle, das bei jedem Aufruf Zonenpunkte mit 40 % Wahrscheinlichkeit auswählt. Das garantiert keine genaue Anzahl und filtert keine Schadensempfänger. Die Spezifikation verwendet die EliteScript-Felderparticle,amountundspeed. Der Partikelhelfer verschiebt Zonenpositionen um einen halben Block auf X und Z; Partikel bilden keine exakten Treffergrenzen für Entitäten ab. -
Schaden --
context.script:damage(cone:full_target(), 1.0, 1.5)trifft aufgelöste Spieler innerhalb der Kegel, weil der Standardfilter der ZonePLAYERist. Die erste Zahl (1.0) ist der übermittelte Basisschaden, die zweite (1.5) der EliteMobs-Schadensmultiplikator für Spieler. Die Kampfverarbeitung bestimmt den endgültigen Schaden.
Dieses Beispiel läuft bei jedem zugestellten Treffer: Es besitzt weder lokalen noch globalen Cooldown und keine Warnwartezeit zwischen Partikeln und Schaden. Ergänze ausdrückliche Cooldown-Prüfungen, wenn du es mit anderen Angriffen kombinierst. Jede Abfrage einer statischen Zone löst ihre Ziele erneut auf; Lua-Zonen-Handles wenden Bedingungen oder Ziel-Caches von YAML-Aktionen nicht automatisch an.
Die an context.script übergebenen Zonen- und Zieltabellen verwenden EliteScript-Feldnamen (targetType, shape, Target, Target2, range, offset, coverage). Die vollständige Liste findest du 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
-
Zustandsinitialisierung --
on_spawnsetztcontext.state.startedauffalseundcontext.state.loop_task_idaufnil. Diestate-Tabelle bleibt zwischen Hooks dieser Fähigkeitslaufzeit auf diesem Boss erhalten. Andere Fähigkeiten haben eigene Zustandstabellen. -
Kampfschutz --
on_enter_combatprüftcontext.state.started, bevor die Schleife gestartet wird. Dies verhindert mehrere überlappende Schleifen, wenn das Ereignis mehr als einmal ausgelöst wird. -
Scheduler-Muster --
context.scheduler:run_every(100, callback)plant den ersten Aufruf ohne Verzögerung und wiederholt dann alle 100 Ticks (5 Sekunden). Es wartet vor dem Start keine fünf Sekunden. Verwende dafürrun_repeating(100, 100, callback). Der Callback erhält einen neuen Kontext ohne auslösendes Ereignis oder Spieler;loop_context.bossist ein neuer Snapshot. Speichere die zurückgegebene numerische Aufgaben-ID im Zustand. Dieses Beispiel wählt nur eine Animation und verursacht selbst keinen Schaden. -
Aufräumen beim Verlassen --
on_exit_combatbricht 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.
Beende kampfgebundene Wiederholungen in on_exit_combat. Ohne diese Bereinigung bleiben sie nach Kampfende geplant, bis sie abgebrochen werden oder die Fähigkeitslaufzeit schließt. Pausierte Laufzeiten überspringen Callbacks, ohne die Aufgabe zu verlieren. Ein nicht abgefangener Callback-Fehler schließt die Laufzeit und beendet ihre Aufgaben.
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
if not context.cooldowns:global_ready() 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
-- Coordinate with other powers that check the shared cooldown
context.cooldowns:set_global(40)
-- 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!")
end
}
Erklärung
-
Nil-Schutz --
context.playerwird aus dem Spieler als Ereignisakteur oder direktem Ziel aufgelöst. Prüfe ihn vor der Verwendung, besonders beim Übertragen des Codes auf andere Hooks. Geplante Callbacks erben diesen Spieler nicht. -
Lokaler Cooldown -- Nach
global_ready()prüftcontext.cooldowns:check_local("fire_touch", 60)seinen Schlüssel und setzt bei Bereitschaft sofort 60 Ticks. Andernfalls gibt esfalsezurück und der Hook endet. Fähigkeiten dieses Bosses teilen den Schlüssel; andere Bosse sind unabhängig. Stelle Schlüsseln beim Kombinieren von Beispielen den Fähigkeitsnamen voran. -
Feuer-Ticks --
context.player:set_fire_ticks(60)setzt die Bukkit-Feuerdauer auf 60 Ticks (3 Sekunden), nachdem geprüft wurde, dass der Spieler gültig, lebendig, online, in der Quellwelt und innerhalb einer Dungeon-Instanz ein aktueller Teilnehmer ist. Es ersetzt die Dauer, statt 60 Ticks hinzuzufügen. Andere Spielmechaniken können das Feuer am Spieler löschen; Nachricht und Partikel beweisen nicht, dass der Effekt angenommen wurde. -
Partikel --
context.world:spawn_particle_at_location(location, spec)spawnt Partikel an einer bestimmten Position. Die Spec-Tabelle akzeptiertparticle(Bukkit-Partikel-Enum-Name),amountundspeed. -
Nachricht --
context.player:send_message(text)sendet eine farbcodierte Chat-Nachricht. Standard-Minecraft-Farbcodes wie&c(rot) funktionieren automatisch. -
Globaler Cooldown --
context.cooldowns:set_global(40)setzt den gemeinsamen Cooldown des Bosses vor den Effekten auf 40 Ticks (2 Sekunden). Dieses Beispiel prüft ihn, bevor es den lokalen Cooldown verbraucht. Andere Lua-Hooks und Callbacks müssen ebenfallsglobal_ready()aufrufen, wenn sie warten sollen; der globale Cooldown hält nicht alle Lua-Callbacks an.
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
if not tick_context.cooldowns:global_ready() then
return
end
-- Check and set this boss's named cooldown before applying effects
if not tick_context.cooldowns:check_local("pulse_aoe", 60) then
return
end
tick_context.cooldowns:set_global(60)
-- 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 a particle burst at the boss; this does not draw the sphere
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "WITCH", amount = 40, speed = 0.1 }
)
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
-
Zustandseinrichtung --
on_spawninitialisiertaoe_task_idmitnil. Diese Task-ID wird die Referenz auf die wiederholende Scheduler-Aufgabe halten. -
Wiederholender Angriff --
on_enter_combatstartet ohne Anfangsverzögerung und wiederholt alle 60 Ticks (3 Sekunden). Die Prüfung verhindert doppelte Schleifen. Jeder Callback prüft den globalen Cooldown, prüft und setzt dannpulse_aoeund setzt den globalen Cooldown vor den Effekten auf 60 Ticks. Das koordiniert nur Fähigkeiten, die diese Schlüssel prüfen; es blockiert nicht automatisch alle Skripte. -
Zonendefinition -- Die
zone_def-Tabelle verwendet die native Lua-Zonensyntax. Daskind-Feld gibt die Form an ("sphere"),radiussetzt die Größe, undoriginwird auf die aktuelle Position des Bosses zum Zeitpunkt des Callback-Aufrufs gesetzt. Das bedeutet, die Zone folgt dem Boss, wenn er sich bewegt. -
Entitätsabfrage --
tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })liefert ein bei eins indiziertes Lua-Array anhand des Entitäts-Überschneidungstests der Form, nicht einer exakten Distanz zu den Füßen. Abfragen suchen in der aktuellen Boss-Welt und filtern Spieler weder nach Spielmodus noch Teilnahme am Kampf. Filter akzeptieren"players","elites","mobs"oder"living"(Standard); der Boss selbst ist ausgeschlossen. -
Schaden verursachen --
victim:deal_custom_damage(4.0)übermittelt überBossCustomAttackDamage4 Basisschaden mit dem Boss als Angreifer. Unverwundbare oder tote Ziele sowie Spieler außerhalb von Überleben und Abenteuer werden übersprungen. Auf dem Elite-zu-Spieler-Pfad umgeht es die normale Formel und Blockreduktion, während Vanilla-Rüstungsreduktionen entfernt werden. Listener, Abbruch und Absorption können den Gesundheitsverlust weiter verändern. Nach der Übermittlung setzt der Helfer den nativen Schadenscooldown des Ziels zurück; der Ablauf unterscheidet sich voncontext.script:damage. -
Partikel-Feedback -- Lila Staub erscheint an der gespeicherten Position jedes gefundenen Spielers, auch wenn dessen Schaden abgelehnt wurde. Ein
WITCH-Partikelausbruch erscheint am Boss; er zeichnet weder einen Ring noch die Kugelgrenze. Diese kompakten Tabellen verwenden standardmäßig keine Positionsstreuung; die Staubgröße ist fest1. -
Aufräumen --
on_exit_combatbricht die wiederholende Aufgabe ab und bereinigt den State, dem gleichen Muster wie im vorherigen Beispiel folgend.
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 -- verwende native Zonen für einfache Formen und context.script, wenn du EliteScripts erweiterte Zielauflösung benötigst.
Beispiel: Mehrstufige Boss-Mechanik
Was dieses Beispiel lehrt: Verwendung von State zur Verfolgung von Boss-Phasen und Verhaltensänderung bei Lebensschwellenwerten.
Dies sind Phasen im Lua-Zustand, keine YAML-phases: von EliteMobs, die eine Boss-Definition ersetzen. Hier wird kein Boss ersetzt oder geheilt. Spätere Heilung führt nicht zu Phase 1 zurück; nach Kampfende bleibt Phase 2 erhalten, bis die Fähigkeitslaufzeit neu erstellt wird.
Vollständige Power-Datei (klicken zum Aufklappen)
local function phase_one_attack(context)
-- One damage pulse per callback; the animation does not delay it
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)
-- One pulse per callback, repeated more frequently in phase 2
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
local function start_attack_loop(context)
if context.state.attack_task_id ~= nil then return end
local phase = context.state.phase or 1
local interval = phase == 2 and 40 or 100
local attack = phase == 2 and phase_two_attack or phase_one_attack
context.state.attack_task_id = context.scheduler:run_every(interval, function(tick_context)
if not tick_context.boss.exists or not tick_context.boss.is_in_combat then return end
if tick_context.cooldowns:check_local("phase_attack", interval) then
attack(tick_context)
end
end)
end
return {
api_version = 1,
on_spawn = function(context)
context.state.phase = 1
context.state.attack_task_id = nil
end,
on_enter_combat = function(context)
start_attack_loop(context)
end,
on_game_tick = function(context)
-- Only check phase transition every 20 ticks (1 second) to stay lightweight
if not context.boss.is_in_combat then return end
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.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_attack_loop(context)
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
-
Zustandsinitialisierung --
on_spawnsetztphaseauf1undattack_task_idaufnil. Diese Werte bleiben zwischen Hooks dieser Fähigkeitslaufzeit erhalten, nicht nach einem Neuladen oder in anderen Fähigkeiten. -
Angriffsschleife --
on_enter_combatsetzt mitstart_attack_loopdie aktuelle Phase fort: Phase 1 verwendet 100 Ticks Intervall, Phase 2 verwendet 40. Beide planen den ersten Callback ohne Verzögerung. Jeder Angriff benötigt weiterhin einen bereitenphase_attack-Cooldown; ein Phasenwechsel löscht den bisherigen Cooldown nicht, sodass ein Callback den Angriff überspringen kann. Dieses Beispiel prüft den globalen Cooldown nicht. Phase 1 übermittelt 3 benutzerdefinierte Schadenspunkte an Spieler in einer Kugel mit Radius 5 und spielt Animation sowie Partikeleffekte, ohne die Animationslänge abzuwarten. -
Phasenprüfung in on_game_tick --
on_game_tickkehrt außerhalb des Kampfes sofort zurück und prüft danach einen Cooldown von 20 Ticks ("phase_check"). Ist der Boss bereits in Phase 2, kehrt es ebenfalls frühzeitig zurück. -
Lebensschwellenwert --
context.boss.health / context.boss.maximum_healthgibt die aktuelle Gesundheit als Bruch an. Wenn sie auf 50% oder darunter fällt, beginnt der Übergang. -
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).
-
Phase-2-Angriffe --
phase_two_attackverwendet eine Kugel mit Radius 8 und übermittelt pro gefundenem Spieler und erfolgreichem Callback einen Treffer mit 2 Schaden. Es versucht, Langsamkeit II für 40 Ticks (amplifier = 1) anzuwenden, unter den oben beschriebenen Teilnahmeprüfungen für Statuseffekte. Danach folgen roter Staub und ein Wither-Sound. Es werden keine mehreren Treffer innerhalb eines Callbacks geplant. -
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. -
Aufräumen --
on_exit_combatbricht die aktuell aktive Angriffsschleife ab, unabhängig davon, in welcher Phase sich der Boss befindet.
Eine aktive, nicht pausierte Laufzeit erhält on_game_tick jeden Server-Tick (20-mal pro Sekunde bei 20 TPS). Begrenze teure Arbeit durch einen Cooldown wie check_local("phase_check", 20). Die Gesundheit wird periodisch abgefragt; ein tödlicher Treffer kann den Boss vor der nächsten Phasenprüfung entfernen. Überschreiten des Ausführungsbudgets schließt diese Fähigkeitslaufzeit und beendet ihre Aufgaben.
KI-Generierungstipps
Wenn du möchtest, dass KI Lua-Fähigkeiten zuverlässig generiert, stelle 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 -- gib an, welche. Native Zonen verwenden
context.zones:get_entities_in_zone(zone_def, opts)mitkind,radius,origin. Script-Utilities verwendencontext.script:zone(...)mit EliteScript-Feldnamen wieshape,Target,Target2. - Lokale und globale Cooldowns -- gib einen Schlüssel mit Präfix und die Dauer in Ticks an.
context.cooldowns:check_local(key, ticks)prüft und setzt einen benannten Cooldown pro Boss. Zur globalen Koordination prüfe ausdrücklichglobal_ready(), bevor der lokale Schlüssel verbraucht wird, und setze die globale Dauer vor den Effekten. - Benutzerdefinierte Modellanimationsnamen -- z.B.
context.boss:play_model_animation("slam"). Die KI kann keine Animationsnamen erraten; stelle sie bereit. - Zielauswahl -- gib 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. - Methoden des richtigen Wrappers verwenden -- gib die passende Boss- oder gemeinsame API-Referenz an und erfinde keine Aliase.
- Script-Utility-Specs verwenden EliteScript-Feldnamen --
targetType,shape,Target,Target2,range,offset,coverage, nicht Lua-artige Namen. - Scheduler-Zeitverhalten und Kontext -- gib an, ob der erste Callback sofort oder verzögert starten soll.
run_everyhat keine Startverzögerung;run_repeatingnimmt sie separat entgegen. Verwende den neuen Callback-Kontext ohne auslösenden Spieler oder Ereignis. Speichere benötigte Spieler-UUIDs oder Positionen ausdrücklich für eine spätere Verwendung.
Gutes Prompt-Beispiel
Schreibe eine Lua-Fähigkeit, die in
on_enter_combateine Aufgabe mit 80 Ticks Startverzögerung und 80 Ticks Intervall startet. Erstelle pro Callback eine native Lua-Kugel mit Radius 6 um den Boss, frage Spieler ab und übermittle jeweils 2.0 benutzerdefinierten Schaden. Prüfe den globalen Cooldown, bevor der lokale Schlüssel"my_power:pulse"für 80 Ticks geprüft und gesetzt wird; setze dann den globalen Cooldown vor den Effekten auf 80. Beende die Aufgabe inon_exit_combat. Erzeuge DUST-Partikel (red=0, green=255, blue=100) an jedem gefundenen Spieler.
Zusätzliche Einschränkungen zum Einbeziehen
- „Gib eine einzelne Tabelle mit
api_version = 1zurü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_combatab." - „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
Verwende diese Checkliste, um eine Lua-Fähigkeit vor dem Einsatz zu überprüfen:
- Die Datei gibt genau eine Tabelle mit
api_version = 1zurück. - Jeder Hook-Name stimmt exakt mit einem Eintrag in der Hook-Liste überein (z.B.
on_player_damaged_by_boss, nichton_player_hit). context.playerwird mit== nilgeschützt, bevor es in Hooks verwendet wird, wo es nil sein kann.context.state-Felder werden inon_spawninitialisiert.- Jede kampfgebundene Wiederholung aus
run_everyoderrun_repeatingwird inon_exit_combatbeendet; das Verhalten der Startverzögerung ist bewusst gewählt. - Scheduler-Callbacks verwenden ihren eigenen Kontext und setzen weder einen auslösenden Spieler noch ein Ereignis voraus.
- Cooldown-Schlüssel tragen den Fähigkeitsnamen als Präfix; Dauern sind in Ticks. Globale Koordination enthält eine ausdrückliche Bereitschaftsprüfung.
on_game_tick-Hooks schützen aufwendige Arbeit hinter einer Cooldown-Prüfung.- Alle Methodennamen existieren in der API-Referenz -- keine erfundenen Aliase.
- Script-Utility-Tabellen verwenden EliteScript-Feldnamen (
targetType,shape,Target, etc.), nicht Lua-artige Namen. - Native Zonendefinitionen verwenden
kind,radius,origin,destination, etc. - Die Fähigkeit ruft keine blockierenden oder lang laufenden Operationen innerhalb eines Hooks oder Callbacks auf.
- Partikelspezifikationen verwenden gültige Bukkit-Partikel-Enum-Namen in UPPER_CASE (z.B.
"FLAME","DUST","EXPLOSION"). - Lade geänderte Dateien neu, spawne einen neuen Boss und prüfe sichtbares Verhalten und Konsole auf einem Staging-Server. Eine erfolgreich gelesene Datei beweist noch keinen funktionierenden Angriff.
Best Practices
-
Beginne mit einem kleinen Hook und verifiziere. Schreibe einen einzelnen
on_spawn, der eine Log-Nachricht sendet. Bestätige, dass er auslöst. Dann baue von dort aus weiter. -
Halte Hilfsfunktionen lokal. Deklariere Helfer wie
local function pick_action(context)vor der Rückgabetabelle, um Abhängigkeiten ausdrücklich zu halten. Jede Fähigkeitsinstanz hat bereits ihre eigene Lua-Laufzeit. -
Lege Geometrie in Script-Utilities. Verwende für Kegel, rotierende oder verschobene Strahlen und animierte Zonen
context.script:zone(...)mit EliteScript-Feldnamen. Die Script-Utilities verwenden die EliteScript-Zonenengine. -
Verwende
context.statefür Laufzeitzustand. Er gehört zu einer Fähigkeitslaufzeit auf einem Boss und bleibt zwischen deren Hooks erhalten. Er wird weder mit anderen Fähigkeiten geteilt noch über Neuladen hinweg gespeichert. -
Verwende benannte lokale Cooldown-Schlüssel. Wähle beschreibende Schlüssel wie
"fire_touch"oder"aoe_pulse". Explizite Schlüssel werden zwischen allen Fähigkeiten desselben Bosses geteilt. Stelle ihnen den Fähigkeitsnamen voran, etwa"my_power:fire_touch", um Kollisionen zu vermeiden. -
Halte
on_game_tickleichtgewichtig. Begrenze aufwendige Arbeit durch einen Cooldown und bleibe deutlich unter dem Ausführungsbudget. -
Beende Wiederholungen, wenn sie nicht mehr benötigt werden. Kampfgebundene Schleifen sollten in
on_exit_combatbeendet werden. Eigene Scheduler-Aufgaben werden beim Schließen der Laufzeit automatisch beendet, laufen aber nach Kampfende ohne ausdrücklichen Abbruch weiter. -
Verwende neue Scheduler-Callback-Kontexte. Callbacks von
run_every,run_repeatingundrun_aftererhalten einen neuen Kontext ohne das planende Ereignis oder dessen Spieler. Verwende dieses Argument und speichere benötigte UUIDs oder Positionen ausdrücklich; ein äußerer Kontext kann veraltete Snapshots enthalten. -
Protokolliere Zustandsübergänge mit
context.log:info(). Erfasse während der Entwicklung Phasenwechsel, Cooldown-Starts und Scheduler-Starts oder -Stopps. Entferne übermäßige Ausgaben vor dem Einsatz: Boss-context.log:debug()schreibt ebenfalls auf Info-Ebene, mit anderem Präfix. -
Verwende die passenden EliteScript-Referenzen. Zonen, Ziele und Relative Vektoren beschreiben die gemeinsamen Parser. Eigenständige Lua-Helfer werten YAML-Bedingungen, Wartezeiten und Aktions-Caches nicht automatisch aus; formuliere Prüfungen in Lua. Vererbte Ziele und Landepositionen benötigen einen YAML-Aktionskontext, der diesen Handles fehlt.
Häufige Anfängerfehler
-
Verwendung des äußeren
contextinnerhalb eines Scheduler-Callbacks. Der äußere Kontext erfasst einen Snapshot zum Zeitpunkt der Hook-Ausführung. Verwende innerhalb einesrun_every- oderrun_after-Callbacks immer den eigenen Parameter des Callbacks (z.B.tick_context), der dir einen frischen Snapshot gibt. -
Vergessen, wiederholende Aufgaben abzubrechen. Wenn du ein
run_everyinon_enter_combatstartest, aber nie abbrichst, läuft die Aufgabe bis der Boss vom Server entfernt wird, auch nach Kampfende. -
State nicht in
on_spawninitialisieren. Wenn ducontext.state.phaseinon_game_tickliest, aber nie inon_spawnsetzt, wird esnilsein und deine Vergleiche werden sich unerwartet verhalten. -
context.playerohne Nil-Prüfung verwenden. Prüfe, welche Hooks einen Spieler bereitstellen. Geplante Callbacks besitzen standardmäßig keinen; eine fehlende Prüfung kann die Fähigkeitslaufzeit schließen. -
Lua-artige Feldnamen in Script-Utilities verwenden. Verwende die dokumentierten Felder
targetType,Target,Target2undshape. Unterstrichvarianten wietarget_typeundzone_shapewerden nicht unterstützt. Der Zonenparser akzeptiert kleingeschriebenestargetundtarget2, aber deshalb keine beliebigen Lua-artigen Aliase. -
Teure Arbeit in
on_game_tickohne Cooldown ausführen. Weltweite Entitätsabfragen und dichte Partikelgeometrie für viele Bosse sind teuer. Begrenze diese Arbeit und halte jeden Callback innerhalb seines Ausführungsbudgets. -
Wrapper-APIs mischen. Boss-spezifische EliteMobs-Wrapper verwenden
entity:teleport_to_location(loc)undplayer:set_velocity_vector(vec). Gemeinsame MagmaCore-Wrapper bieten zusätzliche Aliase. Prüfe die Referenz deines Kontexts, bevor du Methoden aus Beispielen anderer Plugins übernimmst. -
context.boss.healthzum Setzen der Gesundheit verwenden.context.boss.healthist ein schreibgeschützter Snapshot. Um den Boss zu heilen, verwendecontext.boss:restore_health(amount). -
api_version = 1vergessen. Die zurückgegebene Tabelle muss dieses Feld enthalten, oder EliteMobs wird die Fähigkeit nicht laden.
Nächste Schritte
- Erste Schritte -- Dateistruktur, Hooks, erste Power-Erklärung
- NPC-Skripte -- Näherungs- und Interaktionsskripte für NPCs
- Hooks & Lebenszyklus -- vollständige Hook- und Kontextreferenz
- Boss & Entitäten -- Boss-, Spieler- und Entitätsmethoden
- Welt & Umgebung -- Partikel, Sounds, Blitze, Blöcke
- Zonen & Zielerfassung -- native Zonen und Script-Utility-Zonen
- Enums -- gültige Werte für Particle, Sound, Material und andere String-Konstanten
- Fehlerbehebung -- häufige Probleme, Debugging-Tipps und Migrationshinweise
