Lua 腳本:NPC 腳本
EliteMobs 的 NPC Lua 腳本是獨立的 .lua 檔案,可掛接到 NPC 設定上。它們與 Boss Lua 能力是分開的:Boss 能力位於 plugins/EliteMobs/powers/,而 NPC 腳本位於 plugins/EliteMobs/npc_scripts/。
NPC 腳本現在與 Boss 能力、FreeMinecraftModels 道具以及 FMM 物品執行在同一套統一的 MagmaCore 腳本執行環境上。這代表 NPC 腳本可取得完整的共用腳本介面——context.world(包含 strike_lightning)、context.zones、context.scheduler、context.cooldowns、context.log、context.event 與 context.player——再加上一個 NPC 專屬的 context.npc 表。凡是 MagmaCore 對腳本公開的東西,在這裡都能使用。
NPC Lua 腳本仍屬實驗性質。NPC 專屬的鉤子與 context.npc 輔助方法可能會變更。共用的表(context.world、context.zones、context.scheduler、context.cooldowns、context.log、context.event、context.player)與腳本引擎和 Lua API 參考中記載的是同一套。
檔案位置
在下列位置建立 NPC 腳本檔案:
plugins/
EliteMobs/
npc_scripts/
wave.lua
子資料夾會被遞迴掃描。不過腳本是僅以檔名註冊的,所以 npc_scripts/wave.lua 與 npc_scripts/town/wave.lua 會互相衝突——請讓整棵目錄樹中的基本檔名保持唯一。
在 NPC 設定中,.lua 副檔名可省略:- wave 與 - wave.lua 都會解析為 wave.lua。若 NPC 設定參照了不存在的腳本,EliteMobs 會記錄一則警告,該 NPC 仍會照常生成。
將腳本掛接到 NPC
在 NPC 設定中加入 scripts: 清單:
scripts:
- wave.lua
一個 NPC 可掛接多個腳本:
scripts:
- wave.lua
- greeting_particles.lua
腳本會依優先度順序執行。priority 值越低越先執行。若省略 priority,預設為 0。
腳本結構
每個 NPC 腳本都必須回傳一個表:
return {
api_version = 1,
priority = 0,
on_spawn = function(context)
context.state.spawned = true
context.npc:play_model_animation("idle")
end
}
只接受下列頂層欄位:
| 欄位 | 型別 | 說明 |
|---|---|---|
api_version | number | 必填。必須為 1。 |
priority | number | 選填。值越低越先執行。 |
on_spawn | function | 在 NPC 生成後執行。 |
on_remove | function | 在 NPC 被移除時執行。 |
on_game_tick | function | 只要 NPC 有效,每個伺服器 tick 都會執行。請盡量保持極輕量。 |
on_npc_interact | function | 玩家與該 NPC 互動時執行。 |
on_npc_proximity_enter | function | 玩家進入此 NPC 的啟動半徑時執行一次。 |
on_npc_proximity_leave | function | 玩家離開此 NPC 的啟動半徑時執行一次。 |
on_zone_enter | function | 當玩家進入此腳本正在監看的區域時執行(參閱 context.zones)。 |
on_zone_leave | function | 當玩家離開被監看的區域時執行。 |
區域監看只會追蹤玩家——怪物與其他實體永遠不會觸發 on_zone_enter / on_zone_leave。
未知的頂層鍵會在腳本載入時被拒絕。輔助函式應宣告為回傳表上方的 local 函式。
鄰近鉤子
NPC 鄰近鉤子使用 NPC 設定中的 activationRadius 值。
| 鉤子 | 觸發時機 |
|---|---|
on_npc_proximity_enter | 玩家從 NPC 啟動半徑外移動到半徑內。 |
on_npc_proximity_leave | 玩家從 NPC 啟動半徑內移動到半徑外。 |
這些鉤子由伺服器端的鄰近掃描器依「每個 NPC、每位玩家」分別追蹤。站在某個 NPC 附近不會阻止另一個 NPC 觸發自己的進入事件,而停留在半徑內也不會重複洗版觸發進入事件。
原本的問候、對話與任務指示行為仍會照常執行。Lua 鉤子是疊加在其上的額外行為。
共用腳本介面
由於 NPC 腳本執行在統一執行環境上,每個 NPC 鉤子也都會收到 FreeMinecraftModels 腳本所使用的共用 MagmaCore 上下文表。EliteMobs Boss 能力執行在同一套執行環境上,但其中數個表使用的是 Boss 專屬變體。完整方法清單請見 Lua API 參考與腳本引擎:
| 表 | 用途 |
|---|---|
context.world | 世界效果與查詢:strike_lightning、spawn_particle、play_sound、set_block_at、place_temporary_block、spawn_entity、spawn_firework、get_nearby_entities、get_nearby_players、raycast 等等。座標形式(strike_lightning(x, y, z))與位置表形式(strike_lightning_at_location(loc))皆可接受。 |
context.zones | 建立空間區域(create_sphere(x, y, z, radius)、create_cylinder(x, y, z, radius, height)、create_cuboid(x, y, z, xSize, ySize, zSize))——每個都會回傳一個數字控制代碼。watch(handle, on_enter, on_leave) 開始追蹤(回呼會觸發你的 on_zone_enter / on_zone_leave 鉤子,而非你傳入的函式);unwatch(handle) 停止追蹤。 |
context.scheduler | run_later(ticks, fn)、run_repeating(delay, interval, fn)、cancel(task_id)。 |
context.cooldowns | 共用的 MagmaCore 冷卻時間:local_ready、local_remaining、check_local、set_local、global_ready、set_global。 |
context.log | info(msg)、warn(msg)、error(msg) — 寫入伺服器主控台。 |
context.event | 目前的 Bukkit 事件(若存在)。參見下文。 |
context.player | 互動/觸發的玩家(若存在)。參見下文。 |
context.state | 一個普通的 Lua 表,會在此 NPC 腳本實例中持續存在,直到 NPC 被移除。 |
範例:互動時降下雷擊
return {
api_version = 1,
on_npc_interact = function(context)
-- NPC scripts can now reach the full world API.
context.world:strike_lightning_at_location(context.npc:get_location())
end
}
context.npc
context.npc 在每個 NPC 鉤子中都可使用。
欄位
| 欄位 | 型別 | 說明 |
|---|---|---|
name | string | 設定中的 NPC 顯示名稱。 |
filename | string | NPC 設定檔名。 |
uuid | string | 執行期的 NPC UUID。 |
activation_radius | number | 設定的啟動半徑。 |
current_location | location table | 當後端實體存在時的位置快照。 |
entity_type | string | 當後端實體存在時的 Bukkit 實體類型。 |
方法
| 方法 | 參數 | 回傳 | 說明 |
|---|---|---|---|
is_valid() | - | boolean | NPC 是否仍有有效的後端實體。 |
get_location() | - | location table | 目前的 NPC 位置;若實體不可用則回傳生成位置。 |
get_eye_location() | - | location table | 目前的視線位置,後備為生成位置。 |
get_activation_radius() | - | number | 目前設定的啟動半徑。 |
get_nearby_players(radius) | number | table | NPC 半徑範圍內的玩家包裝物件。 |
face_direction_or_location(target) | vector 或 location | nil | 面向一個方向向量,或轉向某個位置/玩家位置。 |
say_greeting(player?) | player、UUID、名稱或 nil | nil | 傳送設定好的問候語。若有觸發玩家則預設為該玩家。 |
say_dialog(player?) | player、UUID、名稱或 nil | nil | 傳送設定好的對話。若有觸發玩家則預設為該玩家。 |
say_farewell(player?) | player、UUID、名稱或 nil | nil | 傳送設定好的告別語。若有觸發玩家則預設為該玩家。 |
play_model_animation(name) | string | nil | 若存在自訂模型動畫則播放。否則為安全的空操作。 |
patrol_pause() | - | boolean | 暫停已設定的巡邏。 |
patrol_resume() | - | boolean | 取消腳本等待或暫時移動並恢復巡邏。 |
walk_to(x, y, z) | 三個數字 | boolean | 走到相對於原點的偏移,然後恢復巡邏。長距離會自動解算。 |
hold(x, y, z) | 三個數字 | boolean | 走到偏移位置並停留。 |
teleport(x, y, z) | 三個數字 | boolean | 目標區域進行實體 tick 時傳送。 |
若 NPC 沒有設定巡邏,或請求無法被接受,移動方法會回傳 false。請參閱 NPC 與首領巡邏。
context.player
context.player 可在 on_npc_interact、on_npc_proximity_enter 與 on_npc_proximity_leave 中使用。在不涉及玩家的生命週期鉤子中,它為 nil。
它是共用的 MagmaCore 玩家包裝物件——與 Boss 能力和 FMM 腳本所用的完整生物/玩家表相同,因此公開的內容遠不只基本項目(生命值、藥水效果、send_message、show_title、show_action_bar、get_held_item、射線偵測等等)。完整清單請見 Lua API 參考。此處常用的有:
| 欄位/方法 | 說明 |
|---|---|
name | 玩家名稱。 |
uuid | 玩家 UUID。 |
current_location | 玩家目前位置的表格;這是欄位,不是方法。 |
get_eye_location() | 目前玩家視線位置。 |
send_message(text) | 傳送聊天訊息。支援顏色代碼。 |
在共用輔助函式中使用 context.player 前,請務必先做 nil 檢查。
entity_type 為小寫在共用的 MagmaCore 實體表上,entity_type 是 Bukkit 名稱的小寫形式("player"、"zombie")。只有 context.npc.entity_type 與 EliteMobs Boss 能力的實體表使用大寫形式。如果腳本必須同時處理兩者,請以不分大小寫的方式比較。
EliteMobs 為每一張共用實體表加入的欄位
當 EliteMobs 正在運行時,它會為每一張 MagmaCore 實體表加入額外欄位——包括 context.player、context.npc:get_nearby_players(...) 回傳的包裝物件,以及 FreeMinecraftModels 道具與物品腳本所看到的那些:
| 欄位 | 型別 | 說明 |
|---|---|---|
is_elite | boolean | 若 EliteMobs 將該實體追蹤為精英,則為 true |
is_custom_boss | boolean | 若它是自訂 Boss 則為 true(當 is_elite 為 false 時永遠是 false) |
is_significant_boss | boolean | 對於生命值倍率大於 1 的自訂 Boss 為 true——也就是實務上「這是真正的 Boss,不是增援」的判斷 |
elite | table 或 nil | 僅存在於精英身上。見下方 |
elite 子表:
| 欄位/方法 | 型別 | 說明 |
|---|---|---|
elite.level | number | 精英等級 |
elite.name | string 或 nil | 精英顯示名稱 |
elite.health | number | 目前精英生命值(即時讀取) |
elite.max_health | number | 精英最大生命值(即時讀取) |
elite.is_custom_boss | boolean | 與最上層同名欄位的值相同 |
elite.health_multiplier | number | 設定的生命值倍率 |
elite.damage_multiplier | number | 設定的傷害倍率 |
elite:remove() | — | 使該精英消失 |
-- Warn the approaching player if a real boss is loose near this NPC
on_npc_proximity_enter = function(context)
if context.player == nil then return end
local here = context.npc:get_location()
local nearby = context.world:get_nearby_entities(here.x, here.y, here.z, 40)
for i = 1, #nearby do
if nearby[i].is_significant_boss then
context.player:send_message("&cA boss is nearby: " .. tostring(nearby[i].elite.name))
return
end
end
end
這些欄位不會出現在 EliteMobs Boss 能力的實體包裝物件上,那些是由另一套 Boss 端的表建構器所建立——該組欄位請見 Boss 與實體。
context.event
當鉤子沒有對應的 Bukkit 事件時,context.event 為 nil。存在時它是共用的 MagmaCore 事件表:
| 欄位/方法 | 說明 |
|---|---|
is_cancelled | 底層事件是否已被取消(僅對可取消的事件有意義)。 |
cancel() | 在事件可取消時取消該事件。 |
uncancel() | 在事件可取消時解除取消。 |
player | 事件的行為者(例如互動中的玩家),以玩家包裝物件形式提供(若存在)。 |
對於互動/鄰近觸發的玩家,請優先使用 context.player(那些鉤子中一定會設定)。
狀態、排程器與冷卻時間
context.state 是一個普通的 Lua 表,會在此 NPC 腳本實例中持續存在,直到 NPC 被移除。
context.scheduler 是共用的 MagmaCore 排程器。MagmaCore 名稱與 EliteMobs 的 run_after / run_every 名稱皆可使用——它們是同一行為的別名:
| 方法 | 參數 | 說明 |
|---|---|---|
run_later(ticks, callback) / run_after(ticks, callback) | number, function | 延遲後執行一次。回傳一個任務 ID。 |
run_repeating(delay, interval, callback) | number, number, function | 在初始延遲後重複執行。回傳一個任務 ID。 |
run_every(interval, callback) | number, function | 每 interval tick 執行一次(初始延遲為 0)。回傳一個任務 ID。 |
cancel(task_id) / cancel_task(task_id) | number | 取消自己擁有的任務。 |
排程器回呼會收到一個全新的 context。它們不會收到原本的 context.player 或 context.event。NPC 被移除時,其擁有的所有任務都會自動取消。
context.cooldowns 是共用的 MagmaCore 冷卻時間表:
| 方法 | 參數 | 回傳 | 說明 |
|---|---|---|---|
local_ready(key?) | string | boolean | 本地冷卻已結束時為 true。 |
local_remaining(key?) | string | number | 剩餘 tick 數,就緒時為 0。 |
check_local(key?, duration) | string, number | boolean | 若已就緒則啟動冷卻並回傳 true。 |
set_local(duration, key?) | number, string | nil | 設定或重設冷卻時間。 |
global_ready() | - | boolean | 共用的全域冷卻就緒時為 true。 |
set_global(duration) | number | nil | 啟動全域冷卻。 |
NPC 腳本現在使用共用的 MagmaCore 冷卻時間參數順序(check_local(key?, duration)),與 Boss 能力和 FreeMinecraftModels 腳本相同。較早的實驗性 NPC 版本使用 check_local(duration, key?) — 請將舊腳本更新為共用順序。
範例:接近時揮手
這個腳本會讓 NPC 面向進入的玩家並播放 wave 自訂模型動畫。只要玩家仍在半徑內,鄰近進入事件對每組 NPC/玩家配對只會觸發一次;冷卻時間則可避免快速離開再進入的循環過於頻繁地重播動畫。
return {
api_version = 1,
priority = 0,
on_npc_proximity_enter = function(context)
if context.player == nil then return end
if context.cooldowns:check_local("wave:" .. context.player.uuid, 60) then
context.npc:face_direction_or_location(context.player.current_location)
context.npc:play_model_animation("wave")
end
end
}
當 NPC 沒有自訂模型,或該模型沒有對應動畫時,play_model_animation(name) 會安全地不執行任何動作。
效能指引
- 保持
on_game_tick鉤子精簡。對每個有定義該鉤子的 NPC 腳本實例而言,它每秒會執行 20 次。(未宣告on_game_tick的腳本永遠不會被 tick。) - 處理鄰近行為時,優先使用
on_npc_proximity_enter與on_npc_proximity_leave,而不是每 tick 輪詢附近玩家。 - 使用
context.cooldowns:check_local(...)來節制動畫、音效與粒子爆發。 - 當某個行為不需要每 tick 執行時,請使用
context.scheduler:run_repeating(...)並設定合理的間隔。 - 避免在 Lua 中進行大範圍搜尋。
context.npc:get_nearby_players(radius)用於小範圍的區域檢查沒問題,但大範圍掃描應留在外掛執行環境中處理。
相關頁面
- 建立 NPC -- NPC 設定欄位,包含
activationRadius - Lua 入門指南 -- Boss Lua 能力
- 腳本引擎 -- 共用的 Lua 概念與統一執行環境
- Lua API 參考 --
context.world、context.player、context.zones等的完整方法清單
