Zum Hauptinhalt springen

Lua-Scripting: Fehlerbehebung

webapp_banner.jpg

Diese Seite behandelt häufige Probleme, die beim Schreiben oder Debuggen von Lua-Fähigkeiten auftreten können, sowie Migrationshinweise für Autoren, die von EliteScript kommen. Wenn du NPC-Skripte debuggst, siehe NPC-Skripte. Wenn du nach funktionierenden Beispielen suchst, siehe Beispiele & Muster. Wenn du gerade erst anfängst, siehe Erste Schritte.

Gemeinsame Lua-Engine

EliteMobs verwendet die MagmaCore-Lua-Scripting-Engine, die von allen Nightbreak-Plugins gemeinsam genutzt wird. Für die Dokumentation zu gemeinsamen Konzepten wie der Sandbox, dem Scheduler, Zonen, der Welt-API, Entitätstabellen und Spieler-UI-Methoden siehe die Seite MagmaCore-Lua-Scripting-Engine.


Häufige Probleme​

1. Fähigkeit lädt überhaupt nicht​

Überprüfe die Serverkonsole auf Fehler beim Serverstart. Die häufigste Ursache ist ein Lua-Syntaxfehler (fehlendes end, nicht übereinstimmende Klammern usw.). Stelle außerdem sicher, dass die Datei auf .lua endet und im richtigen powers-Verzeichnis liegt. Prüfe bei eigenen Dateinamen, ob eine .yml- oder .yaml-Metadatendatei im selben Verzeichnis und mit demselben Basisnamen isEnabled: false setzt; das verhindert die Erkennung. Eine Metadatendatei mit demselben Basisnamen neben einer mitgelieferten Power wendet ihre Felder isEnabled, effect, powerType und die Cooldown-Felder ebenfalls auf diese Power an. Haben zwei Power-Dateien in verschiedenen Ordnern denselben Namen, wird nur eine geladen; die Konsole nennt die übersprungene Kopie.

Die Datei muss eine Tabelle mit api_version = 1 zurückgeben. Diese Tabelle darf nur api_version, das optionale priority und unterstützte Hook-Namen enthalten; Hook-Werte müssen Funktionen sein. Halte Hilfsfunktionen lokal, statt beliebige Schlüssel in die zurückgegebene Tabelle einzufügen. Code auf oberster Ebene läuft sowohl bei der Definitionsprüfung als auch beim Initialisieren der Laufzeitinstanz, ohne Hook-Kontext.

2. Hook wird nie ausgelöst​

Überprüfe, ob der Hook-Name exakt so geschrieben ist, wie er in der Hook-Liste aufgeführt ist. Häufige Fehler: on_boss_hit (falsch) vs. on_boss_damaged_by_player (richtig), oder on_tick (falsch) vs. on_game_tick (richtig).

Ein nicht unterstützter Schlüssel in der zurückgegebenen Tabelle verhindert das Laden der gesamten Definition. Wird sie geladen, aber nicht ausgeführt, prüfe auch, ob der Boss auf den Dateinamen der Fähigkeit verweist, das entsprechende Event auftritt und die Laufzeitinstanz weder fehlgeschlagen noch pausiert ist oder ihre Fähigkeiten unterdrückt werden.

3. context.player ist nil​

Nur Hooks, deren zugrunde liegendes Event einen Spieler mitführt, füllen context.player. Es ist immer nil in on_spawn, on_game_tick, on_boss_damaged, on_boss_damaged_by_elite, on_exit_combat, on_heal, on_death, on_phase_switch und on_mind_action.

Gefüllt ist es in on_boss_damaged_by_player, on_reinforcement_damaged_by_player, on_player_damaged_by_boss, on_enter_combat und on_boss_target_changed sowie in on_zone_enter / on_zone_leave, wenn die beteiligte Entität ein Spieler ist. Prüfe vor der Verwendung immer auf nil. Die vollständige Tabelle findest du unter Hooks & Lebenszyklus.

4. Timeout / Ausführungsbudget überschritten​

Jeder Hook, geplante Callback und jede Dateiauswertung teilt sich mit verschachtelten Aufrufen ein Budget von 250.000 Lua-Instruktionen und 50 ms Thread-CPU-Zeit. Kann die JVM diese CPU-Zeit nicht messen, gilt stattdessen eine Grenze von 250 ms verstrichener Zeit. 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 bricht unkontrollierte Lua-Schleifen während der Ausführung ab. Einen laufenden Java-API-Aufruf kann sie nicht unterbrechen; nach dessen Rückkehr gibt es keine zusätzliche Grenze von 50 ms verstrichener Zeit. Häufige Ursachen sind Schleifen über zu viele Entitäten, Partikel über einer großen Zone ohne einen Wert für coverage und aufwendige Arbeit in jedem Tick. Nutze Cooldowns, weniger Abtastpunkte oder context.scheduler:run_every(...), um Arbeit über mehrere Ticks zu verteilen.

5. Scheduler-Callback verwendet veraltete Daten​

Du verwendest wahrscheinlich den äußeren context anstelle des Callback-Parameters. Ändere function() ... context.boss ... end zu function(tick_context) ... tick_context.boss ... end.

Geplante Callbacks erhalten einen neuen Kontext ohne auslösendes Event, direktes Ziel oder Spieler. Erfasse ein benötigtes Ziel ausdrücklich vor dem Planen und prüfe bei der Verwendung, ob es noch gültig ist. Lies aktuelle Positionen und andere veränderliche Eigenschaften erneut aus, statt einen früheren Tabellenstand zu behalten.

6. Zonenabfrage gibt keine Entitäten zurück​

Überprüfe die Zonendefinition noch einmal genau. Für native Zonen muss kind kleingeschrieben sein ("sphere", nicht "SPHERE"). Für Script-Utilities muss shape großgeschrieben sein ("CONE", nicht "cone"). Überprüfe auch, dass origin oder Target tatsächlich zu einer gültigen Position aufgelöst wird.

Prüfe auch Entitätsfilter und Welt. Native Boss-Zonenabfragen verwenden standardmäßig living, schließen den Boss aus und suchen in seiner Welt. Script-Utility-Zonen verwenden standardmäßig PLAYER. Hole Lua-Zonenziele über full_target() oder border_target() des Zonenobjekts; ein bloßes context.script:target({targetType = "ZONE_FULL"}) hat keine zugeordnete Zone.

7. Partikel erscheinen nicht​

Verwende einen gültigen Bukkit-Particle-Namen; "FLAME" und "flame" werden in Großbuchstaben normalisiert. Prüfe Position, Menge und benötigte Daten. BLOCK benötigt Blockdaten, die dieser Welt-Helper nicht liefert. Nutze eine unterstützte Partikelart oder die Skript-Partikel-API mit Materialoption.

8. Cooldown scheint nicht zu funktionieren​

check_local(key, duration) prüft und setzt den Cooldown in einem Aufruf. Du kannst auch local_ready(key) verwenden und anschließend set_local(duration, key) aufrufen, wenn der Angriff tatsächlich beginnt. Die Bereitschaft zu prüfen startet allein keinen Cooldown. Explizite Schlüssel werden von den Fähigkeiten desselben Bosses gemeinsam verwendet; nutze daher ein Präfix für die jeweilige Fähigkeit. YAML-Metadaten begrenzen die Ausführung von Lua-Hooks nicht automatisch.

9. Boss führt Fähigkeit nach dem Tod weiter aus​

Aufgaben von context.scheduler werden abgebrochen, wenn die Laufzeitinstanz der Fähigkeit geschlossen wird. Brich Angriffsschleifen in on_exit_combat ab, wenn sie schon vor dem Entfernen enden sollen. Prüfe außerdem die aufgerufene Hilfsfunktion: Einige Java-Hilfsfunktionen starten eigene Aufgaben. Beispielsweise führt world:run_fake_gold_nugget_damage(...) bereits eine eigene Schleife pro Tick mit eigenem Timeout aus und darf nur einmal pro Projektilsatz aufgerufen werden.


Fehlermeldungen lesen​

Wenn eine Lua-Fähigkeit zur Laufzeit fehlschlägt, gibt die Konsole einen Fehlerblock mit dem Präfix [Lua] aus, der die Datei und den Hook oder Callback nennt. Eine Zeilennummer wird angegeben, wenn sie aus dem zugrunde liegenden Fehler ermittelt werden kann. Lies vor dem Debuggen die vollständige Meldung. Bei Dateien mit -- @include beziehen sich die gemeldeten Zeilen auf den erweiterten Quelltext.

Ein typischer Fehler sieht so aus:

[Lua] Error in 'push_zone.lua' at line 35 during 'on_boss_damaged_by_player':
[Lua] -> You tried to call a method or function that doesn't exist.
[Lua] -> Check the method name for typos, or make sure you're using ':' (colon) for method calls, not '.' (dot).
[Lua] -> Script has been disabled for this entity to prevent further errors.

Das System übersetzt häufige Lua-Fehler in verständliches Englisch. Hier sind die häufigsten:

Roher Lua-FehlerWas die Konsole dir sagt
attempt to call nilDu hast versucht, eine Methode oder Funktion aufzurufen, die nicht existiert. Prüfe den Methodennamen auf Tippfehler, oder stelle sicher, dass du : (Doppelpunkt) für Methodenaufrufe verwendest, nicht . (Punkt).
index expected, got nilDu hast versucht, auf ein Feld von etwas zuzugreifen, das nil ist. Prüfe, ob vorheriger Code es initialisiert hat.
attempt to indexDu hast versucht, auf eine Eigenschaft eines nil- oder ungültigen Werts zuzugreifen.
bad argumentZeigt die spezifischen Details zur Argument-Abweichung an (erwarteter Typ vs. tatsächlicher Typ).
Budget überschrittenDie Meldung nennt das Instruktionslimit, die CPU-Zeit oder die Ersatzgrenze für verstrichene Zeit; siehe oben.
Tipp

Die fehlerhafte Skriptinstanz wird für diese Entität deaktiviert. Nach dem Korrigieren der Datei muss ihre Definition neu geladen und eine neue Laufzeitinstanz erstellt werden. Eine unveränderte deaktivierte Instanz versucht den fehlerhaften Hook nicht in jedem Tick erneut.


Nimm nicht an, dass undokumentierte Aliasse existieren​

Die Lua-API stellt einen bestimmten Satz von Methodennamen bereit. Wenn du Fähigkeiten von Hand oder mit KI-Unterstützung schreibst, nimm nicht an, dass Kurzformen oder alternative Namen existieren. Die folgenden Namen sind Beispiele für Namen, die nicht existieren und Fehler verursachen:

  • show_temporary_boss_bar() – verwende stattdessen player:show_boss_bar(title, color, style, duration).
  • run_command_as_player() – verwende stattdessen player:run_command(command).
  • em.location(...) – der Methodenname ist falsch. Verwende stattdessen em.create_location(x, y, z) oder context.boss:get_location() / context.player.current_location.
  • em.vector(...) – der Methodenname ist falsch. Verwende stattdessen em.create_vector(x, y, z) oder einfache {x=0, y=1, z=0}-Tabellen.
  • em.zone.sphere(...) – der Methodenname ist falsch. Verwende stattdessen em.zone.create_sphere_zone(radius) oder eine Zonendefinitions-Tabelle wie {kind = "sphere", radius = 5, origin = location}.
  • entity:teleport_to(...) – verwende entity:teleport_to_location(location).
  • Verwende bei den EliteMobs-spezifischen Boss-Entitätswrappern entity:set_velocity_vector(vector). Gemeinsame MagmaCore-Wrapper bieten auch set_velocity(...); prüfe, welchen Wrapper dein Kontext zurückgibt.
  • entity:set_facing(...) – verwende entity:face_direction_or_location(direction_or_location).

Prüfe die API-Referenz des verwendeten Wrappers: Boss & Entitäten, Welt & Umgebung, Zonen & Zielauswahl oder die gemeinsame API. Ähnliche Kontexte können unterschiedliche Methoden anbieten; ein plausibler Alias wird durch seine Schreibweise nicht verfügbar.


Migrationshinweise für EliteScript-Autoren​

Wenn du bereits gute EliteScripts schreibst, ist der einfachste Weg, Lua-Fähigkeiten zu lernen:

  1. Denke weiterhin in Begriffen von Ereignissen, Zielen, Zonen, relativen Vektoren und Partikeln. Die Konzepte sind dieselben – nur die Syntax ändert sich. EliteScript-Ereignisse werden zu Hook-Namen wie on_spawn oder on_boss_damaged_by_player. Ziele und Zonen werden als Tabellen an context.script übergeben, wobei die gleichen Feldnamen verwendet werden, die auf den Seiten EliteScript-Zonen und EliteScript-Ziele dokumentiert sind.

  2. Verlagere deinen Kontrollfluss nach Lua. Zufallswürfe, gemeinsame Hilfsfunktionen, Schleifen, persistenter Zustand (context.state) und Aufgabenplanung (context.scheduler) sind die Dinge, die Lua hinzufügt und die reines EliteScript nicht ohne Weiteres leisten kann. Beginne damit, eine verzweigende oder bedingte Fähigkeit nach Lua zu konvertieren, während du alles andere unverändert lässt.

  3. Verwende context.script für Zielauswahl und Zonengeometrie. Seine Spezifikationstabellen verwenden die tatsächlichen EliteScript-Parser für targetType, shape, Target, Target2, FinalTarget, range, offset, relativeOffset, coverage und filter. Halte die EliteScript-Dokumentation für Feldnamen und Standardwerte offen, lies aber auch die Einschränkungen von Lua-Handles. Statische Lua-Handles führen nicht das Caching der YAML-Aktion mit track: false durch, und übernommene Aktionsdaten fehlen. offset und relativeOffset gelten für aufgelöste Orte, einschließlich LOCATION, LOCATIONS und abgetasteter Zonenorte. Verwende einen ausdrücklich festgelegten Ursprung, wenn ein Bereich an Ort und Stelle bleiben muss.

  4. Achte auf die zwei Zonensysteme. context.script:zone({shape = "SPHERE", ...}) verwendet die EliteScript-Engine mit großgeschriebenen Enums und Target-Spezifikationstabellen. context.zones verwendet kleingeschriebenes kind und einfache origin/destination-Positionen. Vermischte Schlüsselstile können eine leere oder unerwartete Zone, Warnungen oder einen Laufzeitfehler verursachen.


Lernpfad für Anfänger​

Wenn du dieses System von Grund auf lernen möchtest, funktioniert dieser Lernpfad gut:

  1. Schreibe eine Datei mit nur api_version = 1 und on_spawn.
  2. Lass den Boss eine Nachricht senden oder einen Sound abspielen.
  3. Füge einen Cooldown mit context.cooldowns hinzu.
  4. Füge einen spielerausgelösten Hook wie on_boss_damaged_by_player hinzu.
  5. Füge eine verzögerte Aktion mit context.scheduler:run_after(...) hinzu.
  6. Füge eine einfache native Lua-Zonenabfrage oder ein einfaches context.script:target(...) hinzu.
  7. Erst dann gehe zu rotierenden Angriffen, Zustandsmaschinen und mehrstufigen Mechaniken über.

Jeder Schritt baut auf dem vorherigen auf, und du kannst in jeder Phase testen. Versuche nicht, einen mehrphasigen Boss als deine erste Lua-Fähigkeit zu schreiben.


Nächste Schritte​

  • Erste Schritte – Dateistruktur, Hooks, erste Power-Erklärung, Copy-Paste-Vorlagen
  • NPC-Skripte – Skripte für NPC-Nähe, -Interaktion und -Lebenszyklus
  • Beispiele & Muster – vollständige funktionierende Fähigkeiten zum Studieren und Anpassen