Lua 腳本:道具與物品 API
此頁面涵蓋 FreeMinecraftModels 道具與物品腳本可用的所有 API:context.prop、context.item、context.event、context.player、context.world、context.zones、context.scheduler、context.state、context.cooldowns 和 context.log。如果你剛接觸腳本,請先從 入門 開始。
context.prop
prop 表格提供道具實體的資訊以及控制其動畫的方法。為了效能,FMM 會為每個道具快取這個表格;像 current_location 這類欄位是延遲/即時取值的,因此讀取時仍會反映道具當前的狀態。
欄位
| 欄位 | 類型 | 說明 |
|---|---|---|
prop.model_id | string | 藍圖模型名稱(例如 "torch_01") |
prop.current_location | location 表格 | 道具的當前位置。這是即時/延遲取值的欄位,而不是一次性的快照。 |
位置表格擁有標準欄位:x、y、z、world、yaw、pitch。
範例:讀取道具資訊
return {
api_version = 1,
on_spawn = function(context)
context.log:info("Prop spawned: " .. (context.prop.model_id or "unknown"))
local loc = context.prop.current_location
if loc then
context.log:info("Location: " .. loc.x .. ", " .. loc.y .. ", " .. loc.z)
end
end
}
prop:play_animation(name, blend, loop)
在道具模型上播放指定名稱的動畫。
| 參數 | 類型 | 預設值 | 說明 |
|---|---|---|---|
name | string | 必填 | 模型檔案中定義的動畫名稱 |
blend | boolean | true | 是否與當前動畫混合 |
loop | boolean | true | 動畫是否循環 |
如果找到動畫且已啟動則回傳 true,否則回傳 false。
範例
return {
api_version = 1,
on_right_click = function(context)
local success = context.prop:play_animation("open", true, false)
if not success then
context.log:warn("Animation 'open' not found on this model!")
end
end
}
prop:stop_animation()
停止道具上所有當前播放的動畫。
不接受參數。
範例
return {
api_version = 1,
on_right_click = function(context)
context.prop:stop_animation()
end
}
prop:hurt_visual()
在道具上播放視覺受傷動畫(紅色色調閃爍),不會對道具造成任何實際傷害。
不接受參數。
範例
return {
api_version = 1,
on_left_click = function(context)
-- 被打時閃紅,但不實際受到傷害
if context.event then
context.event.cancel()
end
context.prop:hurt_visual()
end
}
prop:pickup()
從世界中移除道具,並在其位置掉落放置用的紙張物品。掉落的物品可以右鍵點擊方塊以再次放置該道具。
不接受參數,也不回傳任何值。移除/掉落的工作會排入 Bukkit 主執行緒。
範例
return {
api_version = 1,
on_right_click = function(context)
-- 讓玩家可以右鍵點擊以拾起道具
context.prop:pickup()
end
}
prop:mount(player)
將玩家放上道具上第一個可用的坐騎點座位。模型必須定義了坐騎點骨骼。
| 參數 | 類型 | 說明 |
|---|---|---|
player | entity 表格 | 玩家實體表格(例如來自 context.player 或 context.event.player) |
當找到該玩家、道具具有坐騎點,且騎乘動作已排入佇列時回傳 true。當這些基本參考無效時回傳 false。回傳 true 並不證明排入佇列的動作實際執行時仍有座位可用。
範例
return {
api_version = 1,
on_right_click = function(context)
local player = context.event and context.event.player
if player then
context.prop:mount(player)
end
end
}
prop:dismount(player)
將玩家從道具上的坐騎點座位移開。
| 參數 | 類型 | 說明 |
|---|---|---|
player | entity 表格 | 玩家實體表格 |
當找到該玩家、道具具有騎乘管理員,且下乘檢查已排入佇列時回傳 true。當這些基本參考無效時回傳 false。
範例
return {
api_version = 1,
on_right_click = function(context)
local player = context.event and context.event.player
if player then
-- 切換騎上/下來
local passengers = context.prop:get_passengers()
for i = 1, #passengers do
if passengers[i].uuid == player.uuid then
context.prop:dismount(player)
return
end
end
context.prop:mount(player)
end
end
}
prop:get_passengers()
回傳一個 Lua 陣列,包含道具上所有目前乘客的實體表格。
不接受參數。
範例
return {
api_version = 1,
on_game_tick = function(context)
local passengers = context.prop:get_passengers()
if #passengers > 0 then
context.log:info("Prop has " .. #passengers .. " passenger(s)")
end
end
}
prop:has_mount_points()
回傳此道具的模型是否定義了坐騎點骨骼。
不接受參數。回傳 true 或 false。
範例
return {
api_version = 1,
on_right_click = function(context)
local player = context.event and context.event.player
if player and context.prop:has_mount_points() then
context.prop:mount(player)
end
end
}
prop:spawn_elitemobs_boss(filename, x, y, z)
在指定位置生成 EliteMobs 自訂 Boss。需要伺服器上安裝 EliteMobs。
| 參數 | 類型 | 說明 |
|---|---|---|
filename | string | 自訂 Boss 的檔名(例如 "my_boss.yml") |
x | number | X 座標 |
y | number | Y 座標 |
z | number | Z 座標 |
回傳已生成 Boss 的生物實體表格,如果未安裝 EliteMobs 或 Boss 檔案不存在則回傳 nil。
範例
return {
api_version = 1,
on_right_click = function(context)
local loc = context.prop.current_location
if loc then
local boss = context.prop:spawn_elitemobs_boss("dungeon_guardian.yml", loc.x, loc.y + 1, loc.z)
if boss then
context.log:info("Spawned boss: " .. (boss.name or "unknown"))
else
context.log:warn("Could not spawn boss -- is EliteMobs installed?")
end
end
end
}
prop:open_inventory(player, title, rows)
為玩家開啟一個持久化的箱子物品欄 GUI。物品欄關閉時內容會儲存到道具的 PersistentDataContainer,再次開啟時會還原。
| 參數 | 類型 | 預設值 | 說明 |
|---|---|---|---|
player | entity 表格 | 必填 | 要顯示物品欄的玩家 |
title | string | 必填 | 物品欄標題(支援 & 顏色代碼) |
rows | int | 3 | 列數(1-6,其中 6 = 54 個格子 = 大型箱子) |
如果道具/玩家的參考有效且開啟動作已排入佇列則回傳 true,否則回傳 false。列數會在建立物品欄之前先被夾限到 1-6 的範圍內。
prop:is_viewing_inventory(player)
回傳指定的玩家目前是否開啟著此道具的物品欄。
| 參數 | 類型 | 說明 |
|---|---|---|
player | entity 表格 | 要檢查的玩家 |
回傳 true 或 false。
範例:物品欄關閉時關閉動畫
context.state["task_" .. player.uuid] = context.scheduler:run_repeating(5, 5, function(tick_context)
if not tick_context.prop:is_viewing_inventory(player) then
tick_context.prop:play_animation("close", true, false)
tick_context.scheduler:cancel(tick_context.state["task_" .. player.uuid])
end
end)
prop:place_book(player)
從玩家的主手取走已寫成或可寫的書,並儲存到道具上。
| 參數 | 類型 | 說明 |
|---|---|---|
player | entity 表格 | 持有書的玩家 |
如果道具/玩家的參考有效且動作已排入佇列則回傳 true。之後在主執行緒上執行的動作,只有在玩家確實持有可寫或已寫成的書、且道具上尚未存放書時才會真正放上去。
prop:read_book(player)
開啟儲存的書讓玩家閱讀。
| 參數 | 類型 | 說明 |
|---|---|---|
player | entity 表格 | 要顯示書的玩家 |
如果道具/玩家的參考有效且閱讀動作已排入佇列則回傳 true。它並不保證確實存在已儲存的書。
prop:take_book(player)
將儲存的書歸還到玩家的物品欄,並從道具中移除。
| 參數 | 類型 | 說明 |
|---|---|---|
player | entity 表格 | 接收書的玩家 |
如果道具/玩家的參考有效且取出動作已排入佇列則回傳 true。它並不保證確實存在已儲存的書。
prop:has_book()
回傳此道具上是否儲存了一本書。不接受參數。
prop:drop_inventory()
在道具的位置以物品實體形式掉落所有儲存的物品欄內容,然後清除儲存的資料。自動關閉任何正在檢視該物品欄的玩家視窗。
不接受參數。如果道具擁有有效的底層 armor stand 且掉落動作已排入佇列則回傳 true。它並不保證確實存在已儲存的物品。
prop:drop_book()
在道具的位置以物品實體形式掉落儲存的書,並清除儲存的書資料。
不接受參數。如果道具擁有有效的底層 armor stand 且掉落動作已排入佇列則回傳 true。它並不保證確實存在已儲存的書。
prop:set_persistent_data(key, value)
將字串值儲存到道具的 armor stand PersistentDataContainer。此資料能在伺服器重啟和區塊卸載後保留。
| 參數 | 類型 | 說明 |
|---|---|---|
key | string | 唯一鍵名(在內部儲存於 fmm_lua_<key>) |
value | string | 要儲存的值。對數字和布林值使用 tostring()。 |
成功時回傳 true,如果道具沒有支援的 armor stand 則回傳 false。
prop:get_persistent_data(key)
擷取先前以 set_persistent_data 儲存的字串值。如果該鍵尚未設定則回傳 nil。
| 參數 | 類型 | 說明 |
|---|---|---|
key | string | set_persistent_data 中使用的鍵名 |
範例:持久化的切換狀態
return {
api_version = 1,
on_spawn = function(context)
local saved = context.prop:get_persistent_data("active")
context.state.active = saved == "true"
end,
on_right_click = function(context)
context.state.active = not context.state.active
context.prop:set_persistent_data("active", tostring(context.state.active))
end
}
context.item
item 表格僅在物品腳本中可用(道具腳本不可用)。它提供自訂物品的資訊與操作該物品的方法。此表格每次鉤子呼叫時都會重新建構。
諸如 set_amount、consume、set_uses、set_name、set_lore 以及耐久度消耗輔助方法等物品寫入方法,會把它們的變更排入 Bukkit 主執行緒並回傳 nil。讀取方法回傳的是它們執行當下已裝備之相符物品的狀態。
欄位
| 欄位 | 類型 | 說明 |
|---|---|---|
item.id | string | 物品類型 ID(來自 YML 設定的 fmm_item_id) |
item:material()
回傳該物品的材料名稱字串(例如 "DIAMOND_SWORD"、"STICK")。
item:get_amount() / item:set_amount(n)
取得或設定物品的堆疊大小。set_amount(n) 會把變更排入佇列並回傳 nil。
| 參數 | 類型 | 說明 |
|---|---|---|
n | int | 新的堆疊數量 |
item:consume(n)
將「把物品的堆疊數量減去 n(預設 1)」排入佇列。如果結果數量為 0 或更少,物品會從玩家的物品欄中移除。回傳 nil。
| 參數 | 類型 | 預設值 | 說明 |
|---|---|---|---|
n | int | 1 | 要消耗的數量 |
item:get_uses() / item:set_uses(n)
取得或設定儲存在物品 PersistentDataContainer 中的自訂使用次數計數器。此計數器獨立於原版耐久度,可用於實作自訂耐久度或充能系統。set_uses(n) 會把變更排入佇列並回傳 nil。
| 參數 | 類型 | 說明 |
|---|---|---|
n | int | 新的使用次數 |
item:get_name() / item:set_name(s)
取得或設定物品的顯示名稱。支援使用 & 的顏色代碼。set_name(s) 會把變更排入佇列並回傳 nil。
| 參數 | 類型 | 說明 |
|---|---|---|
s | string | 新的顯示名稱(例如 "&b&lFrost Sword") |
item:get_lore() / item:set_lore(table)
取得或設定物品的物品說明。get_lore() 回傳字串表格(每行一個)。set_lore() 接受字串表格,會把變更排入佇列並回傳 nil。
| 參數 | 類型 | 說明 |
|---|---|---|
table | table | 字串陣列,每行物品說明一個 |
範例:追蹤使用次數的物品腳本
return {
api_version = 1,
on_right_click = function(context)
local uses = context.item:get_uses()
if uses <= 0 then
context.player:send_message("&cThis item is out of charges!")
return
end
context.item:set_uses(uses - 1)
context.player:send_message("&aUsed! Charges remaining: " .. (uses - 1))
end
}
item:get_durability()
回傳一個包含 current 和 max 欄位的表格,表示物品的原版耐久度,如果該物品沒有耐久度條則回傳 nil。
範例
local dur = context.item:get_durability()
if dur then
context.player:send_message("Durability: " .. dur.current .. "/" .. dur.max)
end
item:get_durability_percentage()
以 0.0 到 1.0 的分數回傳剩餘耐久度,如果該物品沒有耐久度條則回傳 nil。
item:use_durability(amount, can_break)
將「把原版耐久度減少一個固定數值」排入佇列,並回傳 nil。
| 參數 | 類型 | 預設值 | 說明 |
|---|---|---|---|
amount | int | 必填 | 要消耗的耐久度點數 |
can_break | boolean | false | 若為 true,耐久度耗盡時物品會被銷毀。若為 false,耐久度會停在 1。 |
item:use_durability_percentage(fraction, can_break)
將「把原版耐久度減少其最大值的一個百分比」排入佇列,並回傳 nil。
| 參數 | 類型 | 預設值 | 說明 |
|---|---|---|---|
fraction | number | 必填 | 要消耗的最大耐久度的分數(例如 0.1 = 10%) |
can_break | boolean | false | 若為 true,耐久度耗盡時物品會被銷毀。若為 false,耐久度會停在 1。 |
context.event
當前鉤子的事件資料。可用於道具和物品腳本的點擊、戰鬥、互動及一般區域鉤子中。在沒有關聯事件或玩家行為者的鉤子中回傳 nil(on_spawn、on_game_tick、on_destroy、on_equip)。
下方的鉤子參考表格會列出底層的 Bukkit 事件類型以供對照。Lua 包裝層仍然只公開此處列出的欄位與方法。
欄位與方法
| 欄位或方法 | 類型 | 說明 |
|---|---|---|
event.player | 玩家實體表格 | 觸發事件、或跨越受監看一般區域邊界的玩家。可用於道具的 on_left_click、on_right_click、on_zone_enter 與 on_zone_leave,以及由玩家所引發的物品鉤子。請參閱玩家實體方法以了解所有欄位和方法。 |
event.is_cancelled | boolean | context 建構當時的取消狀態。此欄位在呼叫 cancel() 或 uncancel() 之後不會更新。 |
event.cancel() | function | 取消事件(例如防止傷害或互動) |
event.uncancel() | function | 取消先前取消的事件 |
cancel 和 uncancel 會忽略傳給它們的任何參數,因此 context.event.cancel() 和 context.event:cancel() 行為完全相同。FMM 隨附的預製腳本使用冒號形式;本頁使用點號形式。兩者沒有誰比較正確。
並非所有事件都可取消。如果底層的 Bukkit 事件未實作 Cancellable,或該鉤子是背後沒有 Bukkit 事件的一般區域鉤子,event.cancel() 和 event.uncancel() 將不會存在,並且 event.is_cancelled 永遠為 false。
請把 event.is_cancelled 當作初始狀態的快照。如果你的腳本自己呼叫了 event.cancel() 或 event.uncancel(),而你之後在同一個鉤子中還需要記得這個變更,請自行保留一個本地旗標。
目前的 FMM 事件表格不會公開 Bukkit 專屬欄位,例如 target、block、projectile 或 item。當你需要額外的上下文時,請使用 context.player、context.event.player、player:get_target_entity(range)、context.world:raycast(...),或附近實體查詢。
範例:讓道具無敵
範例
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
範例:檢查取消狀態
範例
return {
api_version = 1,
on_left_click = function(context)
if context.event and not context.event.is_cancelled then
context.event.cancel()
context.log:info("Damage cancelled!")
end
end
}
在排程的回呼內部(scheduler:run_later、scheduler:run_repeating),context.event 永遠為 nil。事件修改只能在事件鉤子本身內進行。
context.world
這是 FreeMinecraftModels/MagmaCore 的 world API。請參閱 context.world 以了解完整參考。
全域頁面上記錄的所有方法(get_block_at、set_block_at、spawn_particle、play_sound、strike_lightning、get_time、set_time、get_nearby_entities、get_nearby_players、spawn_entity、get_highest_block_y、raycast、place_temporary_block、drop_item、spawn_firework)在 FMM 中都可用。請參閱 MagmaCore world API 以了解 world:raycast()(投射射線並偵測命中的實體/方塊)、world:place_temporary_block()(暫時性方塊替換)和 world:spawn_firework()(生成具有自訂顏色和形狀的煙火)的完整細節。EliteMobs Boss 能力以同一套 world 基礎為起點,並加上 Boss 專屬的位置表格方法,用於生成 Boss、增援、掉落方塊、暫時性方塊等等;請參閱 EliteMobs World & Environment。
FMM 專屬的 world 附加方法
FreeMinecraftModels 在 context.world 上疊加了三個可選的 EliteMobs 戰利品輔助方法。它們永遠存在,但在未安裝 EliteMobs 時各自都會回傳 false 並且不做任何事:
| 方法 | 說明 |
|---|---|
world:drop_elitemobs_procedural_loot(player, level, location?) | 為該玩家掉落一件程序化生成的 EliteMobs 物品。當程序化物品掉落被停用時回傳 false |
world:drop_elitemobs_random_loot(player, level, location?) | 以指定等級為該玩家擲骰 EliteMobs 的戰利品表 |
world:drop_elitemobs_custom_loot(player, file, level, location?) | 為該玩家掉落指定的 EliteMobs 自訂物品檔案。當檔案無法解析時回傳 false |
完整簽章請見 Lua API 參考。針對 Boss 的道具端等價方法是上文記載的 prop:spawn_elitemobs_boss(...)。
玩家實體方法
玩家實體表格從 context.player、context.event.player 和 context.world:get_nearby_players() 傳回。一般的 MagmaCore on_zone_enter / on_zone_leave 鉤子會把 context.player 與 context.event.player 設為進入或離開的玩家。
全域頁面上記錄的實體表格、生物實體方法、玩家專屬方法和玩家 UI 方法,就是 FMM 所使用的 MagmaCore 表格。EliteMobs Boss 能力公開的是類似但屬於 Boss 專用的實體表格,記錄於 Boss & Entities。請參閱 MagmaCore Lua 腳本引擎 以了解涵蓋實體基本欄位、生物實體欄位和方法、玩家專屬欄位和方法以及 玩家 UI 方法 的完整 FMM 參考。新的玩家方法包括 player:get_target_entity()(光線追蹤瞄準)、player:get_eye_location()、player:get_look_direction()、player:send_block_change()(每位玩家的假方塊)和 player:reset_block() -- 請參閱 玩家專屬方法 以了解詳情。
FMM 專屬實體欄位
每個在 FMM 腳本中建構的實體表格都會自動取得這些額外欄位(透過 FMM 的 LuaEntityEnricher):
| 欄位 | 類型 | 說明 |
|---|---|---|
entity.is_modeled | boolean | 若此 Bukkit 實體是 ModeledEntity 的底層實體則為 true |
entity.is_prop | boolean | 若此實體是支援 PropEntity 的 armor stand 則為 true |
entity.model | 表格或 nil | 僅在 is_modeled = true 時填入(見下方) |
當 entity.model 存在時,它會公開:
| 欄位 / 方法 | 說明 |
|---|---|
model.model_id | 藍圖模型名稱(例如 "dragon") |
model.is_dynamic | 若這是 DynamicEntity(附加到生物實體)則為 true |
model:play_animation(name, blend, loop) | 播放指定名稱的動畫。在實體模型橋接層上,blend 與 loop 預設為 false。成功時回傳 true |
model:stop_animations() | 停止所有當前動畫 |
model:remove() | 立即移除模型化實體及其所有骨骼 |
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
local target = player:get_target_entity(8)
if target and target.is_modeled then
target.model:play_animation("hurt", true, false)
end
end
EliteMobs 實體欄位
當安裝了 EliteMobs 時,FMM 會轉發到 EliteMobs 的 enricher,使相同的實體表格也公開:
| 欄位 | 類型 | 說明 |
|---|---|---|
entity.is_elite | boolean | 若實體由 EliteMobs 追蹤則為 true |
entity.is_custom_boss | boolean | 若它是自訂 Boss 設定則為 true |
entity.is_significant_boss | boolean | 對於 healthMultiplier > 1 的自訂 Boss 為 true(過濾掉雜魚命名怪物) |
entity.elite | 表格或 nil | 僅在 is_elite = true 時填入。包含 level、name、health、max_health、health_multiplier、damage_multiplier、is_custom_boss,加上 elite:remove() |
context.zones
這是 FreeMinecraftModels/MagmaCore 的 zones API。請參閱 context.zones 以了解完整參考。EliteMobs Boss 能力使用的是另一套帶有原生 EliteMobs 區域定義的 context.zones 表格;請參閱 EliteMobs Zones & Targeting。
context.scheduler
此處記錄的 scheduler API 是 FreeMinecraftModels 腳本與 EliteMobs NPC 腳本所使用的 MagmaCore 排程器。EliteMobs 風格的名稱(run_after、run_every 與 cancel_task)在這個共用排程器上是別名,因此兩種命名風格都可以使用。Boss 能力也透過其 Boss 專屬 context 公開相同的別名。請參閱 context.scheduler 以了解完整的 FMM 參考。
context.state
state API 由 FreeMinecraftModels 腳本、EliteMobs Boss 能力與 EliteMobs NPC 腳本共用。請參閱 context.state 以了解完整參考。
context.log
此處記錄的 logging API 是 FreeMinecraftModels/MagmaCore 的記錄器(info、warn、error)。EliteMobs NPC 腳本使用同一個記錄器;EliteMobs Boss 能力則公開 info、warn 與 debug。請參閱 context.log 以了解完整參考。
context.cooldowns
此處記錄的 cooldown API 使用的是 FreeMinecraftModels 腳本與 EliteMobs NPC 腳本共用的 MagmaCore/FMM 參數順序:check_local(key?, duration)。EliteMobs Boss 能力使用相同的參數順序,但搭配 Boss 專屬的後端儲存。請參閱 context.cooldowns 以了解完整參考。
| 方法 | 說明 |
|---|---|
local_ready(key?) | 檢查本地冷卻是否已就緒。 |
local_remaining(key?) | 回傳剩餘的本地冷卻 tick 數,或 0。 |
check_local(key?, duration) | 以原子方式檢查並啟動本地冷卻。 |
set_local(duration, key?) | 設定本地冷卻而不進行檢查。 |
global_ready() | 檢查腳本擁有者的共用全域冷卻。 |
set_global(duration) | 設定腳本擁有者的共用全域冷卻。 |
一般的道具或物品動作冷卻請使用 context.cooldowns:check_local("my_key", 40)。
執行階段模型
每個腳本實例一個執行階段
每個附加了腳本的道具實體都會獲得自己獨立的 Lua 執行階段實例。當道具生成時,FMM 會載入 Lua 原始碼,在一個全新的沙箱化環境中對其求值,並儲存回傳的表格。當道具被移除時,執行階段會關閉。
對於物品腳本,每個(player, itemId)對會建立一個執行階段。當玩家裝備自訂物品時,FMM 會為該玩家和物品類型建立一個腳本實例。當物品被卸下時,執行階段會關閉。
這表示:
- 在檔案範圍宣告的本地變數對該腳本實例是私有的。
context.state在實例之間完全隔離,即使它們共用相同的腳本檔案。
排程任務歸屬
所有透過 context.scheduler 建立的任務都歸屬於建立它們的執行階段。當道具被移除時:
- 執行階段關閉。
- 每個歸屬的任務 -- 一次性與重複的 -- 都會自動取消。
- 所有區域監視都會被清除。
Cooldown 範圍
共用的腳本引擎公開了本地冷卻輔助方法(local_ready、local_remaining、check_local、set_local)與全域冷卻輔助方法(global_ready、set_global)。FMM 將那些儲存範圍劃分如下:
| 腳本類型 | Local 儲存範圍 | Global 儲存範圍 |
|---|---|---|
| 道具腳本 | 每個 ScriptInstance(道具 + 腳本檔) | 每個 PropEntity(與繫結到該道具的每個腳本共用) |
| 物品腳本 | 每個 (player, itemId, scriptFile) 三元組 — 即使物品每次離開有效槽位時腳本實例都被拆除,仍能在重新裝備後持久存在 | 每位玩家(與該玩家執行的每個 FMM 物品腳本共用) |
由於物品腳本在每次裝備/卸下循環時都會被拆除並重建,物品冷卻時間會保存在以玩家 UUID 為鍵的靜態映射中,而不是放在 ScriptInstance 上。這就是為什麼當你把物品換出快捷欄再換回來後,物品冷卻時間仍然有效。
執行預算
每一次鉤子呼叫、每一個排程回呼,以及腳本檔案本身的初次求值,都在一個硬性的執行預算下執行。這個預算是在 Lua VM 內部強制執行的,因此它會在你的程式碼還在跑的時候就生效,而不是事後才看時鐘。
| 限制 | 數值 |
|---|---|
| 目前執行緒 CPU 時間 | 50 毫秒 |
| 執行的 Lua 指令數 | 250,000 |
先觸及哪一個限制,就會以 Lua 錯誤中止該次呼叫並停用該腳本實例。訊息如下:
Lua instruction budget exceeded (250000 instruction limit)
Lua CPU-time budget exceeded (50ms current-thread CPU limit)
由於檢查是逐指令進行的,while true do end 無法凍結伺服器。
預算中的時間部分是以目前執行緒的 CPU 時間衡量,而不是實際經過時間(wall clock),因此當伺服器執行緒被排程移出時,腳本不會被計入那段時間。在無法取得目前執行緒 CPU 計時的 JVM 上,MagmaCore 會退回使用刻意較寬鬆的 250 毫秒經過時間限制(Lua elapsed-time fallback budget exceeded (250ms fallback; current-thread CPU time unavailable)),同時保留同樣的 250,000 指令上限,因此無論哪一種情況,不會終止的腳本都仍受到限制。
嵌套呼叫共用同一份預算:如果某個鉤子呼叫了一個回呼,而該回呼又呼叫了另一個,整條鏈會被當成單一份 50 毫秒 CPU/250,000 指令的額度來計算,而不是每一層各有一份。
在腳本檔案的初次求值期間還沒有實例存在,因此該定義會被拒絕且永遠不會註冊,而不是被停用。
要保持在預算內:
- 避免在鉤子內進行無界迴圈。
- 保持
on_game_tick處理器輕量 -- 它們每個 tick 都會執行。 - 使用
context.scheduler:run_repeating(...)將工作分散到多個 tick。
完整鉤子參考
此表格列出道具與物品腳本所有可用的鉤子。
context.event 欄位描述的是底層的 Bukkit 事件家族。FMM 的 Lua 事件包裝層在適用時仍然只公開 event.player、event.is_cancelled、event.cancel() 與 event.uncancel()。
有效的道具鉤子(7 個)
| 鉤子 | 觸發時機 | context.event |
|---|---|---|
on_spawn | 道具生成到世界中 | nil |
on_game_tick | 每個伺服器 tick(50 毫秒) | nil |
on_destroy | 道具被移除 | nil |
on_left_click | 玩家左鍵點擊道具 | damage 事件 |
on_right_click | 玩家右鍵點擊道具 | interaction 事件 |
on_zone_enter | 玩家進入受監看區域 | 區域玩家行為者(context.player / context.event.player;不可取消) |
on_zone_leave | 玩家離開受監看區域 | 區域玩家行為者(context.player / context.event.player;不可取消) |
目前的腳本驗證器會接受道具腳本中的 on_projectile_hit,但目前的執行環境尚不會把拋射物命中派送給道具腳本。若要讓拋射物行為綁定在腳本化物品上,請使用物品的 on_projectile_hit;若要在外掛端處理模型化實體的拋射物,請使用 Bukkit 的 ModeledEntityHitByProjectileEvent API。
物品鉤子(22 個)
| 鉤子 | 類別 | 觸發時機 | context.event |
|---|---|---|---|
on_attack_entity | 戰鬥 | 玩家攻擊實體 | damage 事件 |
on_kill_entity | 戰鬥 | 玩家擊殺實體 | death 事件 |
on_take_damage | 戰鬥 | 玩家受到傷害 | damage 事件 |
on_shield_block | 戰鬥 | 玩家用盾牌格擋 | damage 事件 |
on_shoot_bow | 戰鬥 | 玩家射出弓箭 | bow shoot 事件 |
on_projectile_hit | 戰鬥 | 玩家的拋射物擊中目標 | projectile hit 事件 |
on_projectile_launch | 戰鬥 | 玩家發射拋射物 | launch 事件 |
on_right_click | 互動 | 玩家右鍵點擊 | interact 事件 |
on_left_click | 互動 | 玩家左鍵點擊 | interact 事件 |
on_shift_right_click | 互動 | 玩家 shift+右鍵點擊 | interact 事件 |
on_shift_left_click | 互動 | 玩家 shift+左鍵點擊 | interact 事件 |
on_interact_entity | 互動 | 玩家右鍵點擊實體 | entity interact 事件 |
on_equip | 裝備 | 物品進入有效槽位 | nil |
on_unequip | 裝備 | 物品離開有效槽位 | nil |
on_swap_hands | 裝備 | 主副手切換 | swap 事件 |
on_drop | 裝備 | 玩家丟棄物品 | drop 事件 |
on_break_block | 公用 | 玩家破壞方塊 | block break 事件 |
on_consume | 公用 | 玩家消耗物品 | consume 事件 |
on_item_damage | 公用 | 物品耐久度受損 | item damage 事件 |
on_fish | 公用 | 玩家使用釣魚竿 | fish 事件 |
on_death | 公用 | 玩家裝備期間死亡 | death 事件 |
on_game_tick | 生命週期 | 裝備期間每個 tick | nil |
後續步驟
/fmm reload 會在重建前取消腳本擁有的工作與冷卻。共用工作包括 run_after、run_every 與 cancel_task;世界提供 raycast、place_temporary_block 和 drop_item。