Zum Hauptinhalt springen

Lua-Scripting: Beispiele & Muster

Diese Seite enthält vollständige, funktionsfähige Beispiele von FreeMinecraftModels-Prop-Skripten sowie praktische Muster und bewährte Vorgehensweisen. Jedes Beispiel enthält eine Erklärung, die beschreibt, was es tut und warum.

Wenn du neu im Prop-Scripting bist, beginne mit Erste Schritte. Für vollständige API-Details siehe die Prop-API.


Beispiel: Unverwundbares Prop

Was dieses Beispiel lehrt: Das einfachste nützliche Skript -- Schaden abbrechen, damit das Prop nicht zerstört werden kann.

Dies ist das vorgefertigte Skript, das mit FreeMinecraftModels ausgeliefert wird.

Vollständige Skriptdatei (zum Aufklappen klicken)
return {
api_version = 1,

on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}

Erklärung

  1. Hook-Wahl -- on_left_click wird ausgelöst, wenn ein Spieler das Prop schlägt (Linksklick). Intern handelt es sich um ein EntityDamageByEntityEvent auf dem zugrunde liegenden Armor Stand des Props.

  2. Event-Prüfung -- context.event sollte in diesem Hook immer vorhanden sein, aber die Prüfung ist eine gute Praxis.

  3. Abbruch -- context.event.cancel() bricht das Schadensereignis ab, wodurch der Armor Stand keinen Schaden nimmt und nicht zerstört wird.

Verwendung

Füge Folgendes zur .yml-Konfiguration deines Props hinzu:

isEnabled: true
scripts:
- invulnerable.lua

Beispiel: Interaktive Tür

Was dieses Beispiel lehrt: Zustand bei Rechtsklick umschalten, Animationen abspielen und stoppen sowie context.state verwenden, um zu verfolgen, ob die Tür offen oder geschlossen ist.

Vollständige Skriptdatei (zum Aufklappen klicken)
local OPEN_ANIMATION = "open"
local CLOSE_ANIMATION = "close"

return {
api_version = 1,

on_spawn = function(context)
context.state.is_open = false
end,

on_left_click = function(context)
-- Make the door invulnerable
if context.event then
context.event.cancel()
end
end,

on_right_click = function(context)
local loc = context.prop.current_location

if context.state.is_open then
-- Close the door
context.prop:stop_animation()
context.prop:play_animation(CLOSE_ANIMATION, true, false)
context.state.is_open = false

if loc then
context.world:play_sound(
"BLOCK_WOODEN_DOOR_CLOSE",
loc.x, loc.y, loc.z,
1.0, 1.0
)
end
else
-- Open the door
context.prop:stop_animation()
context.prop:play_animation(OPEN_ANIMATION, true, false)
context.state.is_open = true

if loc then
context.world:play_sound(
"BLOCK_WOODEN_DOOR_OPEN",
loc.x, loc.y, loc.z,
1.0, 1.0
)
end
end
end
}

Erklärung

  1. Konstanten auf Dateiebene -- OPEN_ANIMATION und CLOSE_ANIMATION werden oberhalb der Return-Tabelle definiert. So lassen sie sich leicht für verschiedene Modelldateien mit unterschiedlichen Animationsnamen anpassen.

  2. Zustandsinitialisierung -- on_spawn setzt context.state.is_open = false. Der Zustand bleibt über alle Hooks für diese Prop-Instanz bestehen.

  3. Unverwundbarkeit -- Der on_left_click-Hook bricht den Schaden ab, damit die Tür nicht versehentlich zerstört werden kann.

  4. Umschaltlogik -- on_right_click prüft context.state.is_open, stoppt die aktuelle Animation, spielt die entsprechende Animation ab, schaltet den Zustand um und gibt einen Sound aus. Der stop_animation()-Aufruf vor play_animation() gewährleistet saubere Übergänge.

  5. Sound-Rückmeldung -- context.world:play_sound() spielt den Bukkit-Sound-Enum-Namen an der Position des Props ab. Sound-Namen müssen UPPER_CASE-Enum-Namen sein.

Animationsnamen

Animationsnamen wie "open" und "close" müssen mit den in der Modelldatei definierten übereinstimmen. Wenn die Animation nicht gefunden wird, gibt play_animation() false zurück und es passiert nichts. Überprüfe deine Modelldatei auf die genauen Animationsnamen.


Beispiel: Näherungsauslöser mit Partikeln

Was dieses Beispiel lehrt: Zonen-Erstellung, Zonenüberwachung für Betreten-/Verlassen-Ereignisse, Partikeleffekte und Bereinigung.

Vollständige Skriptdatei (zum Aufklappen klicken)
local ZONE_RADIUS = 8
local PARTICLE_INTERVAL = 10 -- ticks between particle bursts

return {
api_version = 1,

on_spawn = function(context)
context.state.zone_handle = nil
context.state.particle_task = nil
context.state.players_in_zone = 0

local loc = context.prop.current_location
if loc == nil then return end

-- Create a sphere zone around the prop
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, ZONE_RADIUS)
context.state.zone_handle = handle

-- Watch for enter/leave
context.zones:watch(
handle,
function(player)
-- on_enter
context.state.players_in_zone = (context.state.players_in_zone or 0) + 1
end,
function(player)
-- on_leave
context.state.players_in_zone = math.max(0, (context.state.players_in_zone or 0) - 1)
end
)

-- Start a repeating particle effect at the zone boundary
context.state.particle_task = context.scheduler:run_repeating(0, PARTICLE_INTERVAL, function(tick_context)
local prop_loc = tick_context.prop.current_location
if prop_loc == nil then return end

-- Spawn particles in a ring at the zone boundary
for angle = 0, 350, 30 do
local rad = math.rad(angle)
local px = prop_loc.x + math.cos(rad) * ZONE_RADIUS
local pz = prop_loc.z + math.sin(rad) * ZONE_RADIUS

if (tick_context.state.players_in_zone or 0) > 0 then
-- Red particles when players are inside
tick_context.world:spawn_particle("DUST", px, prop_loc.y + 0.5, pz, 1, 0, 0, 0, 0)
else
-- Green particles when zone is empty
tick_context.world:spawn_particle("HAPPY_VILLAGER", px, prop_loc.y + 0.5, pz, 1, 0, 0, 0, 0)
end
end
end)
end,

on_left_click = function(context)
-- Make invulnerable
if context.event then
context.event.cancel()
end
end,

on_destroy = function(context)
-- Clean up the repeating task
if context.state.particle_task then
context.scheduler:cancel(context.state.particle_task)
context.state.particle_task = nil
end
-- Clean up the zone watch
if context.state.zone_handle then
context.zones:unwatch(context.state.zone_handle)
context.state.zone_handle = nil
end
end
}

Erklärung

  1. Konstanten -- ZONE_RADIUS und PARTICLE_INTERVAL befinden sich auf Dateiebene zur einfachen Anpassung.

  2. Zustandsinitialisierung -- on_spawn setzt alle Zustandsfelder auf nil / 0, bevor irgendetwas anderes geschieht.

  3. Zonen-Erstellung -- context.zones:create_sphere() erstellt eine Kugelzone um das Prop. Der zurückgegebene Handle ist eine numerische ID, um diese Zone später zu referenzieren.

  4. Zonenüberwachung -- context.zones:watch() registriert Callbacks für das Betreten und Verlassen durch Spieler. Die Callbacks erhöhen und verringern einen Zähler, der in context.state gespeichert ist.

  5. Partikelschleife -- Eine wiederkehrende Aufgabe erzeugt alle halbe Sekunde Partikel in einem Ring um das Prop. Der Partikeltyp ändert sich je nachdem, ob sich Spieler in der Zone befinden.

  6. Bereinigung -- on_destroy bricht die wiederkehrende Aufgabe ab und entfernt die Zonenüberwachung. Obwohl beides beim Entfernen des Props automatisch bereinigt wird, ist eine explizite Bereinigung eine bewährte Vorgehensweise.

Partikel-Performance

Das Erzeugen vieler Partikel bei jedem Tick kann die Leistung beeinträchtigen. Verwende ein angemessenes Intervall (10-20 Ticks) und halte die Partikelanzahl niedrig. Das obige Beispiel verwendet PARTICLE_INTERVAL = 10 (zweimal pro Sekunde) mit nur 12 Partikeln pro Ring.


Beispiel: Sound-erzeugendes Prop

Was dieses Beispiel lehrt: Sounds bei Interaktion abspielen, Cooldown-ähnliches Verhalten mit State und Scheduler sowie die Verhinderung von Schnellklick-Interaktionen.

Vollständige Skriptdatei (zum Aufklappen klicken)
local SOUND_NAME = "BLOCK_NOTE_BLOCK_HARP"
local COOLDOWN_TICKS = 40 -- 2 seconds between sounds

return {
api_version = 1,

on_spawn = function(context)
context.state.on_cooldown = false
end,

on_left_click = function(context)
-- Make invulnerable
if context.event then
context.event.cancel()
end
end,

on_right_click = function(context)
-- Prevent spam
if context.state.on_cooldown then
return
end

local loc = context.prop.current_location
if loc == nil then return end

-- Play the sound
context.world:play_sound(SOUND_NAME, loc.x, loc.y, loc.z, 1.0, 1.0)

-- Show some particles
context.world:spawn_particle("NOTE", loc.x, loc.y + 1.5, loc.z, 5, 0.3, 0.3, 0.3, 0)

-- Set cooldown
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}

Erklärung

  1. Cooldown-Muster -- Da FMM-Prop-Skripte keine eingebaute context.cooldowns-API wie EliteMobs haben, implementiert das Beispiel einen einfachen Cooldown mit context.state.on_cooldown und scheduler:run_later(). Das Flag wird auf true gesetzt, wenn der Sound abgespielt wird, und eine verzögerte Aufgabe setzt es nach COOLDOWN_TICKS zurück.

  2. Sound-Wiedergabe -- context.world:play_sound() nimmt den Bukkit-Sound-Enum-Namen in UPPER_CASE, Koordinaten, Lautstärke und Tonhöhe entgegen.

  3. Partikel-Rückmeldung -- Notenpartikel erscheinen über dem Prop, wenn der Sound abgespielt wird, als visueller Hinweis.

  4. Unverwundbarkeit -- Der on_left_click-Hook bricht den Schaden wie gewohnt ab.

  5. Scheduler-Callback-Kontext -- Der run_later-Callback erhält later_context, einen frischen Kontext. Wir verwenden later_context.state (nicht context.state), um das Cooldown-Flag zurückzusetzen. Da der Zustand geteilt wird, zeigen beide auf dieselbe Tabelle -- aber die Verwendung des Callback-Kontextparameters ist die korrekte Gewohnheit.

Cooldown-Implementierung

FMM-Prop-Skripte haben nicht die context.cooldowns-API von EliteMobs. Verwende das hier gezeigte Muster: ein boolesches Flag in context.state kombiniert mit scheduler:run_later() zum Zurücksetzen. Dies gibt dir volle Kontrolle über Cooldown-Dauer und -Verhalten.


Beispiel: Animiertes Umgebungs-Prop

Was dieses Beispiel lehrt: Eine Schleifenanimation beim Spawnen starten, mit einem Tick-basierten Partikelemitter.

Vollständige Skriptdatei (zum Aufklappen klicken)
return {
api_version = 1,

on_spawn = function(context)
-- Start the idle animation immediately, looping
context.prop:play_animation("idle", false, true)

-- Emit ambient particles every 40 ticks (2 seconds)
context.state.ambient_task = context.scheduler:run_repeating(0, 40, function(tick_context)
local loc = tick_context.prop.current_location
if loc == nil then return end

tick_context.world:spawn_particle(
"ENCHANT",
loc.x, loc.y + 1, loc.z,
10, 0.5, 0.5, 0.5, 0.05
)
end)
end,

on_left_click = function(context)
if context.event then
context.event.cancel()
end
end,

on_destroy = function(context)
if context.state.ambient_task then
context.scheduler:cancel(context.state.ambient_task)
end
end
}

Erklärung

  1. Automatischer Animationsstart -- on_spawn spielt sofort eine Schleifenanimation "idle" ab. Das false für Blend bedeutet, dass sie ohne Überblendung einer vorherigen Animation startet. Das true für Loop bedeutet, dass sie sich endlos wiederholt.

  2. Umgebungspartikel -- Eine wiederkehrende Aufgabe erzeugt alle 2 Sekunden Verzauberungstisch-Partikel über dem Prop und schafft einen magischen Umgebungseffekt.

  3. Bereinigung -- on_destroy bricht die Partikelaufgabe ab.


Bewährte Vorgehensweisen

  • Mit einem kleinen Hook beginnen und überprüfen. Schreibe einen einzelnen on_spawn, der eine Log-Nachricht sendet. Bestätige, dass er ausgelöst wird. Dann baue darauf auf.

  • Hilfsfunktionen lokal halten. Deklariere Helfer wie local function toggle_door(context) oberhalb der Return-Tabelle. So bleiben sie außerhalb des globalen Gültigkeitsbereichs.

  • Alle Zustände in on_spawn initialisieren. Wenn du context.state.is_open in on_right_click liest, aber nie in on_spawn setzt, wird es nil sein und deine Vergleiche könnten sich unerwartet verhalten.

  • Wiederkehrende Aufgaben abbrechen, wenn sie nicht mehr benötigt werden. Jedes run_repeating sollte ein passendes cancel in on_destroy haben. Nicht abgebrochene Aufgaben verschwenden CPU.

  • Frische Scheduler-Callback-Kontexte verwenden. Scheduler-Callbacks erhalten einen frischen Kontextparameter. Verwende diesen Parameter immer innerhalb des Callbacks, nicht den äußeren context.

  • on_game_tick leichtgewichtig halten. Wenn du diesen Hook definierst, wird er jeden Server-Tick ausgeführt (20 Mal pro Sekunde). Schütze aufwendige Arbeit hinter einer zustandsbasierten Cooldown-Prüfung.

  • Props standardmäßig unverwundbar machen. Sofern du nicht möchtest, dass das Prop zerstörbar ist, füge die on_left_click-Schadensabbruch-Logik in jedes Skript ein.

  • UPPER_CASE für Bukkit-Enums verwenden. Sound-Namen und Partikel-Namen müssen das Bukkit-Enum-Konstantenformat verwenden (z. B. "FLAME", nicht "flame").


Häufige Anfängerfehler

  • Den äußeren context innerhalb eines Scheduler-Callbacks verwenden. Der äußere Kontext erfasst einen Schnappschuss zum Zeitpunkt, als der Hook ausgeführt wurde. Innerhalb von Callbacks immer den eigenen Parameter des Callbacks verwenden.

  • Vergessen, wiederkehrende Aufgaben abzubrechen. Wenn du ein run_repeating in on_spawn startest, aber nie abbrichst, läuft die Aufgabe, bis das Prop entfernt wird.

  • Zustand nicht in on_spawn initialisieren. Das Lesen von context.state.x vor dem Setzen gibt nil zurück, was deine Logik stillschweigend brechen kann.

  • Falsche Animationsnamen. Wenn play_animation("open") false zurückgibt, stimmt der Animationsname nicht mit dem in der Modelldatei überein. Überprüfe das Modell auf die genauen Namen.

  • Sound-/Partikelnamen in Kleinbuchstaben. "flame" funktioniert nicht -- verwende "FLAME". Die API konvertiert Partikel intern in UPPER_CASE, aber Sound-Enum-Namen müssen exakt sein.

  • api_version = 1 vergessen. Die zurückgegebene Tabelle muss dieses Feld enthalten, sonst lädt FMM das Skript nicht.

  • Funktionen in der zurückgegebenen Tabelle platzieren, die keine Hooks sind. Hilfsfunktionen müssen oberhalb der return-Anweisung deklariert werden. Nur Hook-Namen (on_spawn, on_right_click usw.) sind als Schlüssel in der zurückgegebenen Tabelle erlaubt.


QC-Checkliste

Verwende diese Checkliste, um ein Prop-Skript vor dem Einsatz zu überprüfen:

  1. Die Datei gibt genau eine Tabelle mit api_version = 1 zurück.
  2. Jeder Hook-Name stimmt genau mit einem Eintrag in der Hook-Liste überein.
  3. context.event wird mit if context.event then geprüft, bevor cancel() aufgerufen wird.
  4. context.state-Felder werden in on_spawn initialisiert.
  5. Jeder scheduler:run_repeating(...)-Aufruf hat ein passendes scheduler:cancel(...) in on_destroy.
  6. Scheduler-Callbacks verwenden den eigenen Kontextparameter des Callbacks, nicht den äußeren context.
  7. on_game_tick-Hooks schützen aufwendige Arbeit hinter einer Prüfung.
  8. Alle Methodennamen existieren in der Prop-API-Referenz -- keine erfundenen Aliase.
  9. Sound- und Partikelnamen verwenden UPPER_CASE-Bukkit-Enum-Namen.
  10. Das Skript ruft keine blockierenden oder lang laufenden Operationen innerhalb eines Hooks oder Callbacks auf.

Tipps zur KI-Generierung

Wenn du möchtest, dass eine KI zuverlässig Prop-Skripte generiert, stelle sicher, dass der Prompt Folgendes enthält:

  • Exakter Hook-Name -- z. B. on_right_click, nicht "wenn der Spieler das Prop klickt".
  • Animationsnamen aus der Modelldatei -- die KI kann diese nicht erraten; gib sie an.
  • Sound-Enum-Namen -- z. B. "BLOCK_NOTE_BLOCK_HARP", nicht "Harfenklang".
  • Partikel-Enum-Namen -- z. B. "FLAME", nicht "Feuerpartikel".
  • Ob das Prop unverwundbar sein soll -- wenn ja, on_left_click mit context.event.cancel() einbeziehen.
  • Nur dokumentierte Methodennamen verwenden -- wenn es nicht auf der Prop-API-Seite steht, existiert es nicht.

Gutes Prompt-Beispiel

Schreibe ein FMM-Prop-Skript, das die Animation "activate" bei Rechtsklick abspielt, das Prop unverwundbar macht, FLAME-Partikel an der Prop-Position beim Klick erzeugt, den BLOCK_LEVER_CLICK-Sound abspielt und einen 2-Sekunden-Cooldown zwischen Klicks mit context.state und scheduler:run_later hat.


Nächste Schritte

  • Erste Schritte -- Dateistruktur, Hooks, erste Skript-Erklärung, Vorlagen
  • Prop-API -- vollständige API-Referenz für alle Context-Tabellen
  • Fehlerbehebung -- häufige Probleme, Debugging-Tipps