Lua 腳本:範例與模式
本頁介紹 FreeMinecraftModels 道具腳本的範例、寫法與注意事項。請準備範例引用的模型和動畫,先在伺服器驗證效果,再加入地圖。
範例:無敵道具
學習內容: 用最小的腳本取消左鍵點擊的預設傷害。
將範例儲存為腳本資料夾中的 invulnerable.lua,重新載入腳本後再綁定至道具。
完整腳本檔案(點擊展開)
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
逐步講解
- 玩家攻擊道具時觸發
on_left_click。Lua 事件是 FMM 預設道具傷害處理使用的取消旗標。 - 此鉤子應當總是提供
context.event,但先確認是否存在仍是好習慣。 context.event.cancel()阻止 FMM 套用這次點擊的預設傷害,不會讓道具免疫所有傷害來源。
用法
在道具的 .yml 設定加入:
isEnabled: true
scripts:
- invulnerable.lua
範例:互動式門
學習內容: 右鍵切換狀態、播放和停止動畫,以及用 context.state 記錄開關狀態。
完整腳本檔案(點擊展開)
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)
-- Prevent default left-click damage
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
}
逐步講解
OPEN_ANIMATION和CLOSE_ANIMATION定義在回傳的表外,方便依模型的動畫名稱調整。on_spawn將context.state.is_open初始化為false。狀態在此道具的同一腳本綁定內跨鉤子保留,同一道具的其他腳本各有自己的狀態。on_left_click取消這次點擊的預設傷害。on_right_click請求停止和播放對應動畫,切換旗標並播放音效。旗標記錄請求的狀態,不代表動畫已播放。blend = true將動畫排入佇列;停止請求不能中斷共用引擎正在播放的death。詳見動畫。此例不改變屏障碰撞,也不保證門口可以通行。- 音效接受不區分大小寫的 Bukkit 列舉名稱、
minecraft:block.note_block.harp等有效鍵及自訂資源包音效鍵。用戶端資源包必須包含對應音效。
"open"、"close" 必須符合模型定義。找不到時 play_animation() 回傳 false,但本例仍會切換旗標並播放音效。若狀態取決於請求是否被接受,請檢查回傳值。即使回傳 true,也不代表用戶端已顯示動畫。
範例:帶粒子的鄰近觸發器
學習內容: 建立區域、監看進入和離開、產生粒子,以及清理資源。
完整腳本檔案(點擊展開)
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
-- Pass placeholder functions to enable on_zone_enter and on_zone_leave.
-- The actual logic lives in the hooks below.
context.zones:watch(handle, function() end, function() 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
-- Flame particles when players are inside
tick_context.world:spawn_particle("FLAME", 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)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_zone_enter = function(context)
context.state.players_in_zone = (context.state.players_in_zone or 0) + 1
end,
on_zone_leave = function(context)
context.state.players_in_zone = math.max(0, (context.state.players_in_zone or 0) - 1)
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
}
逐步講解
ZONE_RADIUS和PARTICLE_INTERVAL是檔案層級的常數,方便調整。on_spawn先將狀態欄位初始化為nil或0。context.zones:create_sphere()在道具生成時的位置建立固定球形區域,數值 handle 指向該區域。粒子環讀取道具目前位置,因此應搭配靜止道具使用,或在移動道具時更新區域。context.zones:watch(handle, function() end, function() end)啟用on_zone_enter和on_zone_leave。鉤子調整context.state中的計數器,也透過context.player/context.event.player提供穿越邊界的玩家,可用來撰寫玩家專屬邏輯。- 重複任務每 10 個伺服器 tick 產生一圈粒子,20 TPS 時為半秒。區域內有無玩家決定粒子類型。
on_destroy取消重複任務和區域監看。移除道具時系統也會自動清理,但明確清理仍是好習慣。
每個 tick 產生大量粒子會影響效能。可使用 10 到 20 ticks 的間隔,並控制數量。本例 PARTICLE_INTERVAL = 10,20 TPS 時每秒兩次,每圈 12 個粒子。
範例:發聲道具
學習內容: 互動時播放音效,以及使用 context.cooldowns 限制連續點擊。
完整腳本檔案(點擊展開)
local SOUND_NAME = "BLOCK_NOTE_BLOCK_HARP"
local COOLDOWN_TICKS = 40 -- 2 seconds between sounds
return {
api_version = 1,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
-- Prevent spam
if not context.cooldowns:check_local("sound", COOLDOWN_TICKS) 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)
end
}
逐步講解
context.cooldowns:check_local("sound", COOLDOWN_TICKS)檢查冷卻,可用時立即啟動。同一道具的此腳本綁定由所有玩家共用冷卻。每個 tick 代表 50 ms 的經過時間;排程器延遲則計算實際伺服器 tick。- 音效接受不區分大小寫的 Bukkit 列舉名稱、
minecraft:block.note_block.harp等有效鍵及自訂資源包音效鍵。用戶端必須有對應音效。 - 播放音效時,道具上方出現音符粒子作為提示。
on_left_click取消這次點擊的預設傷害。
一般冷卻使用 context.cooldowns。只有延遲結束時需要自訂狀態變更,才使用 context.state 配合 scheduler:run_later()。
範例:動畫環境道具
學習內容: 生成時啟動循環動畫,並按 tick 間隔產生環境粒子。
完整腳本檔案(點擊展開)
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
}
逐步講解
on_spawn立即啟動"idle"。blend 為false表示立即切換。內建idle狀態始終循環,不受傳入的 loop 引數影響。- 重複任務每 2 秒在道具上方產生附魔台粒子,營造魔法效果。
on_destroy取消粒子任務。
範例:可坐椅子
學習內容: 右鍵就座、左鍵起身,以及透過 context.event.player 取得互動玩家。
完整腳本檔案(點擊展開)
return {
api_version = 1,
on_right_click = function(context)
-- Handle seating here instead of also using the default mount fallback
if context.event then
context.event.cancel()
end
local player = context.event and context.event.player
if not player then return end
-- Check if the player is already sitting on this prop
local passengers = context.prop:get_passengers()
for i = 1, #passengers do
if passengers[i].uuid == player.uuid then
-- Player is already seated, do nothing on right-click
return
end
end
-- Mount the player on the chair
context.prop:mount(player)
local loc = context.prop.current_location
if loc then
context.world:play_sound("BLOCK_WOOD_PLACE", loc.x, loc.y, loc.z, 0.8, 1.2)
end
end,
on_left_click = function(context)
-- Cancel default left-click damage
if context.event then
context.event.cancel()
end
local player = context.event and context.event.player
if not player then return end
-- Check if the player is sitting and dismount them
local passengers = context.prop:get_passengers()
for i = 1, #passengers do
if passengers[i].uuid == player.uuid then
context.prop:dismount(player)
local loc = context.prop.current_location
if loc then
context.world:play_sound("BLOCK_WOOD_BREAK", loc.x, loc.y, loc.z, 0.8, 1.0)
end
return
end
end
end
}
逐步講解
on_right_click從context.event.player取得玩家,確認尚未就座後,透過context.prop:mount(player)請求第一個空閒m_掛載點座位。on_left_click取消這次點擊的預設傷害,若玩家正在乘坐,則呼叫context.prop:dismount(player)讓玩家下來。context.prop:get_passengers()回傳實體表陣列,透過 UUID 找到互動玩家。- 音效伴隨排入佇列的就座和起身請求,不代表操作已完成。右鍵鉤子取消 FMM 預設就座處理,避免再次嘗試。模型必須有空閒
m_掛載點。
範例:祝福神壇
學習內容: 檢查和消耗手持物品、隨機藥水效果、冷卻及粒子和音效回饋。
完整腳本檔案(點擊展開)
local COOLDOWN_TICKS = 600 -- 30 seconds between uses
local BLESSINGS = {
{ effect = "speed", name = "Swiftness" },
{ effect = "strength", name = "Strength" },
{ effect = "regeneration", name = "Regeneration" },
{ effect = "resistance", name = "Resistance" },
{ effect = "jump_boost", name = "Leap" },
{ effect = "haste", name = "Haste" },
}
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
-- Check cooldown
if context.state.on_cooldown then
player:send_message("&eThe shrine is recharging... Please wait.")
return
end
-- Check if the player is holding a gold ingot
local held = player:get_held_item()
if not held or held.type ~= "GOLD_INGOT" then
player:send_message("&eThe shrine demands a gold offering...")
return
end
-- Consume one gold ingot
player:consume_held_item(1)
-- Play blessing effects
local loc = context.prop.current_location
if loc then
context.world:spawn_particle("ENCHANT", loc.x, loc.y + 1.5, loc.z, 30, 0.5, 0.5, 0.5, 0.5)
context.world:spawn_particle("HAPPY_VILLAGER", loc.x, loc.y + 1, loc.z, 10, 0.3, 0.3, 0.3, 0)
context.world:play_sound("BLOCK_BEACON_ACTIVATE", loc.x, loc.y, loc.z, 1.0, 1.5)
end
-- Apply a random blessing
local chosen = BLESSINGS[math.random(#BLESSINGS)]
player:add_potion_effect(chosen.effect, 600, 1) -- 30 seconds, level II
player:send_message("&aThe shrine blesses you with " .. chosen.name .. "!")
-- Set cooldown
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}
逐步講解
player:get_held_item()回傳主手物品的type、amount、display_name,空手則回傳nil。held.type是大寫 Bukkit 材質名稱,因此與"GOLD_INGOT"比較。player:consume_held_item(1)將主手物品移除任務排入佇列,執行時才讀取主手,不回傳付款結果。本例不是原子交易。玩家更換手持物品可能改變消耗對象,而祝福立即套用。- 檔案層級的
BLESSINGS表列出正面效果,math.random(#BLESSINGS)隨機選擇。player:add_potion_effect(effect, duration, amplifier)套用效果,600ticks 為 30 秒,amplifier1為 II 級。 - 此腳本綁定的所有玩家共用旗標,600 個伺服器 tick 後重設,20 TPS 時為 30 秒。按經過時間計算的冷卻可使用發聲道具範例中的
context.cooldowns。 - 附魔和開心村民粒子配合烽火台啟動聲,呈現祝福效果。
範例:詛咒神壇
學習內容: 負面藥水效果、雷擊、生成實體,以及依貢品選擇分支。
完整腳本檔案(點擊展開)
local COOLDOWN_TICKS = 600 -- 30 seconds between uses
local BUFFS = {
{ effect = "speed", name = "Swiftness" },
{ effect = "strength", name = "Strength" },
{ effect = "regeneration", name = "Regeneration" },
{ effect = "resistance", name = "Resistance" },
}
local CURSES = {
{ effect = "slowness", name = "Slowness" },
{ effect = "weakness", name = "Weakness" },
{ effect = "poison", name = "Poison" },
{ effect = "mining_fatigue", name = "Mining Fatigue" },
}
local ZOMBIE_COUNT = 4
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
-- Check cooldown
if context.state.on_cooldown then
player:send_message("&7The dark shrine pulses with residual energy...")
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Check if the player is holding a gold ingot
local held = player:get_held_item()
if not held or held.type ~= "GOLD_INGOT" then
-- No offering -- punish the player!
player:send_message("&4The shrine demands tribute! You dare approach empty-handed?!")
-- Strike lightning on the player
local player_loc = player.current_location
if player_loc then
context.world:strike_lightning(player_loc.x, player_loc.y, player_loc.z)
end
-- Spawn a horde of zombies around the shrine
for i = 1, ZOMBIE_COUNT do
local angle = math.rad((360 / ZOMBIE_COUNT) * i)
local spawn_x = loc.x + math.cos(angle) * 3
local spawn_z = loc.z + math.sin(angle) * 3
context.world:spawn_entity("zombie", spawn_x, loc.y, spawn_z)
end
-- Apply a random curse
local chosen_curse = CURSES[math.random(#CURSES)]
player:add_potion_effect(chosen_curse.effect, 400, 1) -- 20 seconds, level II
player:send_message("&cThe shrine curses you with " .. chosen_curse.name .. "!")
-- Ominous effects
context.world:spawn_particle("SMOKE", loc.x, loc.y + 1, loc.z, 30, 0.5, 0.5, 0.5, 0.05)
context.world:play_sound("ENTITY_WITHER_AMBIENT", loc.x, loc.y, loc.z, 1.0, 0.5)
else
-- Gold offered -- reward the player
player:consume_held_item(1)
-- Apply a random buff
local chosen_buff = BUFFS[math.random(#BUFFS)]
player:add_potion_effect(chosen_buff.effect, 600, 1) -- 30 seconds, level II
player:send_message("&aThe dark shrine accepts your offering. You are blessed with " .. chosen_buff.name .. "!")
-- Positive feedback
context.world:spawn_particle("ENCHANT", loc.x, loc.y + 1.5, loc.z, 30, 0.5, 0.5, 0.5, 0.5)
context.world:play_sound("BLOCK_BEACON_ACTIVATE", loc.x, loc.y, loc.z, 1.0, 0.8)
end
-- Set cooldown regardless of path
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}
逐步講解
- 腳本檢查
player:get_held_item(),沒有金錠時懲罰,有金錠時獎勵。 context.world:strike_lightning(x, y, z)在player.current_location產生會造成傷害的真實雷擊。context.world:spawn_entity("zombie", x, y, z)在神壇周圍生成一圈普通殭屍。移除道具不會移除這些生物。本例不檢查地形,也不限制存活數量。player:add_potion_effect("poison", 400, 1)套用 20 秒的中毒 II。效果名稱是對應 BukkitPotionEffectType的小寫名稱。- 獎勵分支將主手消耗排入佇列並套用隨機正面效果。與祝福神壇一樣,這只是示範,不是付款和獎勵的原子交易。
- 兩個分支都設定同一腳本綁定內所有玩家共用的旗標,600 個伺服器 tick 後重設,20 TPS 時為 30 秒。
同時生成多個實體可能影響伺服器效能。數量宜控制在 4 到 6 個;多人同時使用時,可考慮額外的個別玩家冷卻。
範例:旋轉地球儀
學習內容: 互動時請求定時動畫、安排停止任務並播放機械音效。
完整腳本檔案(點擊展開)
local SPIN_ANIMATION = "spin"
local SPIN_DURATION = 100 -- 5 seconds in ticks
return {
api_version = 1,
on_spawn = function(context)
context.state.is_spinning = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
-- Prevent starting a new spin while already spinning
if context.state.is_spinning then
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Start the spin animation (non-looping)
context.prop:play_animation(SPIN_ANIMATION, true, false)
context.state.is_spinning = true
-- Play a mechanical clicking sound
context.world:play_sound("BLOCK_CHAIN_PLACE", loc.x, loc.y, loc.z, 1.0, 1.5)
-- Schedule the animation to stop after 5 seconds
context.scheduler:run_later(SPIN_DURATION, function(later_context)
later_context.prop:stop_animation()
later_context.state.is_spinning = false
local stop_loc = later_context.prop.current_location
if stop_loc then
later_context.world:play_sound("BLOCK_CHAIN_FALL", stop_loc.x, stop_loc.y, stop_loc.z, 1.0, 0.8)
end
end)
end
}
逐步講解
context.state.is_spinning防止重複旋轉請求,開始時設定,排程停止時清除。play_animation(SPIN_ANIMATION, true, false)透過佇列請求非循環動畫。停止任務在點擊後 100 個伺服器 tick 執行,不是從實際播放開始計時。佇列忙碌可能縮短可見旋轉,甚至使它無法顯示;共用引擎也不會停止正在播放的death。本例在動畫請求回傳false時仍設定旗標。BLOCK_CHAIN_PLACE提供機械啟動聲,BLOCK_CHAIN_FALL提供停止聲,可調整音調。run_later回呼內的狀態與世界存取都使用later_context,不使用外部context。
"spin" 必須符合模型定義。若模型使用 "rotate" 或 "turn",請修改 SPIN_ANIMATION。
範例:驚嚇道具
學習內容: 區域觸發、長冷卻,以及組合音效、粒子和動畫製造驚嚇。
完整腳本檔案(點擊展開)
local SCARE_RADIUS = 3
local COOLDOWN_TICKS = 1200 -- 60 seconds between scares
local SCARE_ANIMATION = "jumpscare"
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
context.state.zone_handle = nil
local loc = context.prop.current_location
if loc == nil then return end
-- Create a small sphere zone around the prop
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, SCARE_RADIUS)
context.state.zone_handle = handle
-- Watch for players entering the zone. The actual scare logic
-- lives in on_zone_enter below.
context.zones:watch(handle, function() end, nil)
end,
on_zone_enter = function(context)
if context.state.on_cooldown then
return
end
local scare_loc = context.prop.current_location
if scare_loc == nil then return end
-- Play the jumpscare animation
context.prop:stop_animation()
context.prop:play_animation(SCARE_ANIMATION, false, false)
-- Scary sound
context.world:play_sound(
"ENTITY_GHAST_SCREAM",
scare_loc.x, scare_loc.y, scare_loc.z,
1.0, 0.7
)
-- Burst of smoke particles
context.world:spawn_particle(
"CAMPFIRE_SIGNAL_SMOKE",
scare_loc.x, scare_loc.y + 1, scare_loc.z,
20, 0.5, 0.5, 0.5, 0.05
)
-- Set cooldown so it does not trigger again immediately
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_destroy = function(context)
-- 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
}
逐步講解
context.zones:create_sphere()在生成位置建立半徑 3 格的固定區域,context.zones:watch(handle, function() end, nil)啟用on_zone_enter。移動道具時也要移動或重建區域。on_zone_enter播放"jumpscare"動畫、惡魂尖叫並產生營火煙霧,製造突然的驚嚇。- 所有玩家共用此腳本綁定的旗標。1200 個伺服器 tick 後重設,20 TPS 時為 60 秒;之後下一次進入區域可再次觸發。
context.zones:watch()第三個引數為nil,表示不處理玩家離開。on_destroy取消區域監看。移除道具時也會自動清理,但明確清理仍是好習慣。
將道具藏在轉角或暗處。半徑 3 格讓玩家靠近後才觸發,可依需要調整 SCARE_RADIUS 和 COOLDOWN_TICKS。
範例:哥布林生成器道具
學習內容: 透過 prop:spawn_elitemobs_boss() 在互動時生成自訂 Boss,並處理 EliteMobs 無法使用的情況。
完整腳本檔案(點擊展開)
local BOSS_FILE = "goblin_warrior.yml"
local COOLDOWN_TICKS = 200 -- 10 seconds between spawns
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
-- Prevent spawn spam
if context.state.on_cooldown then
player:send_message("&7The spawner is recharging...")
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Try to spawn the EliteMobs boss
local boss = context.prop:spawn_elitemobs_boss(BOSS_FILE, loc.x, loc.y + 1, loc.z)
if boss then
-- Success -- play spawn effects
context.world:spawn_particle("FLAME", loc.x, loc.y + 1, loc.z, 20, 0.5, 0.5, 0.5, 0.05)
context.world:play_sound("ENTITY_EVOKER_PREPARE_SUMMON", loc.x, loc.y, loc.z, 1.0, 1.0)
player:send_message("&cA goblin warrior emerges!")
else
-- EliteMobs is not installed or the boss file was not found
context.log:warn("Could not spawn boss '" .. BOSS_FILE .. "' -- is EliteMobs installed?")
player:send_message("&7The spawner fizzles... (EliteMobs not available)")
-- Fizzle particles as visual feedback
context.world:spawn_particle("SMOKE", loc.x, loc.y + 1, loc.z, 10, 0.3, 0.3, 0.3, 0.02)
context.world:play_sound("BLOCK_FIRE_EXTINGUISH", loc.x, loc.y, loc.z, 0.8, 1.2)
end
-- Set cooldown
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}
逐步講解
context.prop:spawn_elitemobs_boss(filename, x, y, z)在指定座標生成 EliteMobs 自訂 Boss。檔名須符合 EliteMobscustombosses資料夾中的.yml檔案。- 整合功能無法回傳活著的 Boss 時回傳
nil,包括外掛無法使用、設定缺失或生成失敗。範例訊息建議檢查 EliteMobs,不代表已確定具體原因。 - 請求的位置在道具上方一格。這不是地形或碰撞檢查,請為 Boss 選擇合適的空地。
- 同一腳本綁定的所有玩家共用每 200 個伺服器 tick 一次的嘗試,20 TPS 時為 10 秒。它不限制存活 Boss 數,道具消失也不會移除 Boss。用於重複遊玩時應增加遭遇數量等限制。
- 成功時使用火焰與喚魔者召喚聲,失敗時使用煙霧與滅火聲,讓玩家不用查看主控台也能區分結果。
"goblin_warrior.yml" 等檔名必須對應既有自訂 Boss 設定。散布使用此腳本的地圖或地城時,請附帶 Boss 設定並註明 EliteMobs 相依需求。
物品效果
裝備物品腳本範例已停用。目前的物品效果請使用自訂物品定義和自訂附魔。
最佳實踐
- 從小鉤子開始驗證。 先寫一個輸出日誌的
on_spawn,確認觸發後再擴充。 - 輔助函式保持區域作用域。 在回傳表外宣告
local function toggle_door(context),避免污染全域作用域。 - 在
on_spawn初始化狀態。 若未設定context.state.is_open就在on_right_click讀取,它會是nil,比較結果可能不符預期。 - 及時取消重複任務。 每個
run_repeating應在on_destroy有對應的cancel,避免浪費 CPU。 - 使用回呼自己的上下文。 排程器會提供新的 context 引數,回呼內使用它,不要擷取外部
context。 - 保持
on_game_tick輕量。 它每個伺服器 tick 執行,20 TPS 時每秒 20 次。用context.cooldowns或排程間隔限制高負荷工作。 - 明確控制左鍵傷害。 不希望攻擊造成預設道具傷害時,在
on_left_click中取消;這並不阻止所有傷害來源。 - 粒子名稱接受
FLAME和flame,API 統一大小寫後查找 Bukkit 粒子。音效接受不區分大小寫的 Bukkit 列舉名稱、minecraft:block.note_block.harp等有效鍵及自訂資源包音效鍵。用戶端必須有對應音效。
常見新手錯誤
- 在排程回呼中使用外部
context。 它保留鉤子執行時的快照,應使用回呼自己的引數。 - 忘記取消重複任務。
on_spawn啟動的run_repeating若不取消,會持續到道具移除。 - 未初始化狀態。 設定前讀取
context.state.x會得到nil,可能使邏輯出錯而沒有明顯提示。 - 動畫名稱錯誤。
play_animation("open")回傳false時,請檢查模型中的正確名稱。 - 粒子名稱接受
FLAME和flame,API 統一大小寫後查找 Bukkit 粒子。音效接受不區分大小寫的 Bukkit 列舉名稱、minecraft:block.note_block.harp等有效鍵及自訂資源包音效鍵。用戶端必須有對應音效。 - 忘記
api_version = 1。 回傳的表必須包含該欄位,否則 FMM 不載入腳本。 - 把輔助函式放進回傳表。 除
api_version和可選的priority外,只能放入支援的鉤子名稱及函式值。輔助函式應在表外以區域函式定義。FMM 按設定清單順序執行綁定腳本,priority不會改變順序。
QC 檢查清單
部署道具腳本前請檢查:
- 檔案只回傳一個包含
api_version = 1的表。 - 所有鉤子名稱準確符合道具鉤子列表。附魔鉤子屬於另一個執行環境。
- 在已知點擊鉤子以外取消事件前,檢查
context.event和context.event.cancel。 context.state欄位在on_spawn初始化。- 每個
scheduler:run_repeating(...)都在on_destroy有對應的scheduler:cancel(...)。 - 排程回呼使用自己的上下文引數,不使用外部
context。 on_game_tick對高負荷工作設有條件檢查。- 方法名稱均存在於道具 API參考中,沒有編造的別名。
- 粒子名稱接受
FLAME和flame,API 統一大小寫後查找 Bukkit 粒子。音效接受不區分大小寫的 Bukkit 列舉名稱、minecraft:block.note_block.harp等有效鍵及自訂資源包音效鍵。用戶端必須有對應音效。 - 鉤子與回呼內沒有阻塞或長時間執行的操作。
AI 生成提示
請在產生道具腳本的提示詞中提供:
- 準確的鉤子名稱,例如
on_right_click,而不只是「玩家點擊時」。 - 模型中的動畫名稱,AI 無法猜測。
- 音效列舉名稱,例如
"BLOCK_NOTE_BLOCK_HARP",而不只是「豎琴聲」。 - 粒子列舉名稱,例如
"FLAME",而不只是「火焰粒子」。 - 左鍵是否造成預設傷害。需要阻止時使用
on_left_click和context.event.cancel()。 - 只使用已驗證的方法名稱。優先參照道具 API;需要的操作未列出時,應檢查目前執行環境的原始碼,不要編造別名。
良好的提示詞範例
撰寫一個 FMM 道具腳本,右鍵時播放 "activate" 動畫,取消左鍵預設傷害,點擊時在道具位置產生 FLAME 粒子並播放 BLOCK_LEVER_CLICK,用 context.cooldowns:check_local("activate", 40) 設定兩次點擊間的 2 秒冷卻。
物品內容相容性
使用 scripts: 的舊物品包需要更新物品定義和附魔內容。FMM 跳過這些已停用的定義,保留原檔不變。加入 material: 已不能啟用物品腳本。