跳至主要內容

Lua 腳本:NPC 腳本

webapp_banner.jpg

EliteMobs 的 NPC Lua 腳本是獨立的 .lua 檔案,可掛接到 NPC 設定上。它們與 Boss Lua 能力是分開的:Boss 能力位於 plugins/EliteMobs/powers/,而 NPC 腳本位於 plugins/EliteMobs/npc_scripts/。

NPC 腳本與 Boss 能力及 FreeMinecraftModels 道具使用共用的 MagmaCore 腳本執行環境。它們會收到 context.world、context.zones、context.scheduler、context.cooldowns、context.log 等共用服務,以及 NPC 專屬的 context.npc 表。玩家與事件資料取決於鉤子。Boss 能力為數個表提供了自己的版本,因此編寫 NPC 腳本時請使用共用 API 的參數格式。

實驗性功能

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 值越低越先執行,省略時預設為 0。優先度相同時保留 scripts: 清單的順序。這決定的是 NPC 事件的分派順序,不是獨立排程工作的順序。


腳本結構​

每個 NPC 腳本都必須回傳一個表:

return {
api_version = 1,
priority = 0,

on_spawn = function(context)
context.state.spawned = true
context.npc:play_model_animation("idle")
end
}

只接受下列頂層欄位:

欄位型別說明
api_versionnumber必填。必須為 1。
prioritynumber選填。值越低越先執行。
on_spawnfunction在 NPC 生成後執行。
on_removefunction在正常移除 NPC 時執行,早於腳本關閉及實體移除。
on_game_tickfunction腳本執行環境初始化且時鐘啟動後,每個伺服器 tick 執行。請保持輕量。
on_npc_interactfunction一般互動處理到達腳本步驟時執行;限制見下文。
on_npc_proximity_enterfunction玩家進入此 NPC 的啟動半徑時執行一次。
on_npc_proximity_leavefunction玩家離開此 NPC 的啟動半徑時執行一次。
on_zone_enterfunction當玩家進入此腳本正在監看的區域時執行(參閱 context.zones)。
on_zone_leavefunction當玩家離開被監看的區域時執行。

區域監看只會追蹤玩家——怪物與其他實體永遠不會觸發 on_zone_enter / on_zone_leave。

未知的頂層鍵會在腳本載入時被拒絕。輔助函式應宣告為回傳表上方的 local 函式。

載入器會執行檔案層級程式碼進行驗證,接著各 NPC 腳本實例會在自己的沙箱中再次執行。不要在檔案層級初始化時使用鉤子的上下文。每個腳本都有獨立的全域變數及 context.state,同一 NPC 上的腳本也不例外。

NPC 執行環境採用延遲初始化。目前 NPC 宿主會建立實例並分派 on_spawn,卻不會呼叫執行環境主動初始化的 start() 方法。因此,只有 on_game_tick 的檔案不會自行開始 tick。請定義 on_spawn = function(context) end,在生成時初始化,或由另一個已宣告的事件鉤子稍後初始化。

Lua 錯誤會關閉受影響的腳本實例,並取消它擁有的工作。後續 NPC 事件會丟棄已關閉的實例,不會重新建立。請重新載入修正後的定義,並建立新的 NPC 腳本實例。狀態不會跨移除或重新啟動保存;on_remove 是 NPC 移除鉤子,不是通用錯誤或關閉回呼。


鄰近鉤子​

NPC 鄰近鉤子使用 NPC 設定中的 activationRadius 值。

鉤子觸發時機
on_npc_proximity_enter玩家從 NPC 啟動半徑外移動到半徑內。
on_npc_proximity_leave玩家從 NPC 啟動半徑內移動到半徑外。

伺服器端掃描器每 100 ticks 檢查一次各 NPC/玩家配對,20 TPS 時通常為五秒,距離以目前 NPC 實體周圍的球形範圍計算。第一次掃描時已在範圍內也算進入。兩次掃描之間的短暫造訪可能漏掉。靠近一個 NPC 不會阻止另一個 NPC 的進入事件,持續偵測到在範圍內也不會重複觸發。

正在巡邏移動或編輯中的 NPC 會被略過。因此,先前偵測到的配對可能在移動開始時產生離開事件,在恢復偵測時產生進入事件。移除 NPC 或玩家登出不保證觸發離開鉤子:分派需要追蹤中的 NPC 與線上玩家都存在。NPC 集合或線上玩家集合為空時,會直接清除追蹤,不發送離開事件。activationRadius 小於等於零會停用鄰近啟動;任務提供者獨立的 30 格標記範圍不會擴大 Lua 啟動範圍。

原本的問候、對話與任務指示行為仍會照常執行。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) 建立空間區域。回傳數字控制代碼,擁有者沒有世界時回傳 nil。watch(handle, on_enter, on_leave) 在對應參數為函式時啟用頂層鉤子,不會呼叫傳入的函式本身。unwatch(handle) 刪除區域並停止追蹤。
context.schedulerrun_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.loginfo(msg)、warn(msg)、error(msg) — 寫入伺服器主控台。
context.event鉤子提供事件或行為者時的相應資料。參見下文。
context.player互動/觸發的玩家(若存在)。參見下文。
context.state一個普通的 Lua 表,會在此 NPC 腳本實例中持續存在,直到 NPC 被移除。

共用區域採用建立時 NPC 所在世界的絕對座標,不會跟隨移動的 NPC。建立時便會註冊每 tick 掃描,檢查擁有者目前世界中的玩家,即使尚未透過 watch 加入進入/離開處理。請為要啟用的每個方向傳入函式,並在回傳的腳本表中實作對應的 on_zone_enter 或 on_zone_leave。這些鉤子收到 context.player 與行為者事件表,但沒有區域識別碼。unwatch 會完整刪除控制代碼;再次監看需要另建區域。第一次掃描時已在區域內的玩家算作進入,而離線玩家無法收到離開回呼。

範例:互動時降下雷擊​

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 鉤子中都可使用。

欄位​

欄位型別說明
namestring設定中的 NPC 顯示名稱。
filenamestringNPC 設定檔名。
uuidstring執行期的 NPC UUID。
activation_radiusnumber設定的啟動半徑。
current_locationlocation table當後端實體存在時的位置快照。
entity_typestring當後端實體存在時的 Bukkit 實體類型。

方法​

方法參數回傳說明
is_valid()-booleanNPC 是否仍有有效的後端實體。
get_location()-location table目前的 NPC 位置;若實體不可用則回傳生成位置。
get_eye_location()-location table目前的視線位置,後備為生成位置。
get_activation_radius()-number目前設定的啟動半徑。
get_nearby_players(radius)numbertableNPC 所在世界中球形距離範圍內的玩家包裝物件。不篩選遊戲模式;請使用非負半徑。
face_direction_or_location(target)vector、location 或 entity tablenil面向方向或同一世界中的位置。含 world 或 current_location 的表視為位置,只有座標則視為方向。巡邏正在移動時不做任何事。
say_greeting(player?)player、UUID、名稱或 nilnil傳送設定好的問候語。若有觸發玩家則預設為該玩家。
say_dialog(player?)player、UUID、名稱或 nilnil傳送設定好的對話。若有觸發玩家則預設為該玩家。
say_farewell(player?)player、UUID、名稱或 nilnil傳送設定好的告別語。若有觸發玩家則預設為該玩家。
play_model_animation(name)stringnil若存在自訂模型動畫則播放。否則為安全的空操作。
patrol_pause()-boolean暫停已設定的巡邏。
patrol_resume()-boolean取消腳本等待或暫時移動並恢復巡邏。
walk_to(x, y, z)三個數字boolean走到相對於設定生成點的偏移,然後恢復巡邏。自動解算長距離路線。
hold(x, y, z)三個數字boolean走到相對於設定原點的偏移並停留。
teleport(x, y, z)三個數字boolean傳送到相對於設定原點的偏移,目標區域必須進行實體 tick。
remove()-nil透過原因 OTHER 的正常移除流程移除目前 NPC 實體,不刪除預設的放置資料。不要從 on_remove 呼叫,否則會再次進入移除流程。

若 NPC 沒有設定巡邏控制器,或請求無法被接受,移動方法會回傳 false。路線設定與長距離行為請參閱 NPC 與首領巡邏。


context.player​

context.player 可在 on_npc_interact、on_npc_proximity_enter、on_npc_proximity_leave 及監看區域的進入/離開鉤子中使用。在不涉及玩家的生命週期鉤子中,它為 nil。

這是 FMM 腳本也使用的共用 MagmaCore 玩家包裝物件。它提供生命值、藥水效果、send_message、show_title、show_action_bar、get_held_item、射線偵測等。Boss 能力有自己的玩家包裝物件,其方法清單不能與此互換。完整共用清單請見 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_eliteboolean若 EliteMobs 將該實體追蹤為精英,則為 true
is_custom_bossboolean若它是自訂 Boss 則為 true(當 is_elite 為 false 時永遠是 false)
is_significant_bossboolean生命值倍率大於 1 的自訂 Boss 為 true。不檢查是否作為增援召喚。
elitetable 或 nil僅存在於精英身上。見下方
elite_playertable 或 nil在 EliteMobs 建立的共用玩家包裝物件上,若玩家的精英物品欄已載入,則提供 tier。每次存取都會建立新表,儲存的表仍保留原本的 tier。
can_receive_hostile_effect(actor_uuid)boolean依共用敵對目標規則與 EliteMobs 魔法武器授權,檢查玩家行為者。請傳入有效的 UUID 字串。

elite 子表:

欄位/方法型別說明
elite.levelnumber精英等級
elite.namestring 或 nil精英顯示名稱
elite.healthnumber目前精英生命值(即時讀取)
elite.max_healthnumber精英最大生命值(即時讀取)
elite.is_custom_bossboolean與最上層同名欄位的值相同
elite.health_multipliernumber設定的生命值倍率
elite.damage_multipliernumber設定的傷害倍率
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 能力包裝物件使用獨立的建構器與不同的欄位組。部分名稱重疊,例如 is_elite,但共用的巢狀 elite 表不能與 Boss 能力方法互換。相關清單請見 Boss 與實體。FMM 提供自己的 EliteMobs 配接器;不要假設 EliteMobs 建立的共用包裝物件上的每個附加欄位都存在於 FMM。


context.event​

只有鉤子既沒有事件也沒有行為者時,context.event 才為 nil。它是共用的 MagmaCore 事件表:

欄位/方法說明
is_cancelled建立表時的取消狀態快照,不可取消的事件為 false。呼叫 cancel() 或 uncancel() 後不會更新。
cancel()僅在底層事件可取消時存在,用來取消該事件。
uncancel()僅在底層事件可取消時存在,用來清除取消狀態。
player事件的行為者(例如互動中的玩家),以玩家包裝物件形式提供(若存在)。

對於互動/鄰近觸發的玩家,請優先使用 context.player(那些鉤子中一定會設定)。

目前 on_npc_interact 只提供玩家,不提供 Bukkit 事件。因此事件表有 player 及 is_cancelled: false,但沒有 cancel 或 uncancel 方法。NPC 互動腳本不能透過此表取消一般 NPC 選單。

互動流程會忽略已取消的 Bukkit 互動,阻止使用命名牌或與編輯中的 NPC 互動,並為每位玩家施加一 tick 的互動去重。若任務對話目標處理了本次互動,就會在 Lua 鉤子之前返回。否則鉤子會先於一般 NPC 選單/動作分派執行。鄰近事件也不可取消;區域鉤子只提供行為者,沒有 Bukkit 事件。這些流程同樣不提供 cancel() 或 uncancel()。


狀態、排程器與冷卻時間​

context.state 屬於這個獨立的 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?)stringboolean本地冷卻已結束時為 true。
local_remaining(key?)stringnumber剩餘 tick 數,就緒時為 0。
check_local(key?, duration)string, numberboolean若已就緒則啟動冷卻並回傳 true。
set_local(duration, key?)number, stringnil設定或重設冷卻時間。
global_ready()-boolean共用的全域冷卻就緒時為 true。
set_global(duration)numbernil啟動全域冷卻。

check_local 的時間一律為第二個參數,預設鍵請寫成 check_local(nil, 60)。set_local 則將時間放在第一位。預設鍵包含腳本檔名。同一 NPC 配接器上的腳本共用明確指定的本地鍵與全域冷卻儲存區;除非有意協調,否則請使用不同的鍵。時間依單調時鐘的經過時間計算,每個名義 tick 為 50 ms,不是計算伺服器 tick;非正時間會清除冷卻。設定全域冷卻不會自動阻止鉤子或 check_local,請自行檢查 global_ready()。

統一冷卻 API

NPC 腳本現在使用共用的 MagmaCore 冷卻時間參數順序(check_local(key?, duration)),與 Boss 能力和 FreeMinecraftModels 腳本相同。較早的實驗性 NPC 版本使用 check_local(duration, key?) — 請將舊腳本更新為共用順序。


範例:接近時揮手​

此腳本讓 NPC 面向偵測到進入的玩家,並播放 wave 自訂模型動畫。鄰近進入取決於五秒掃描,不是每個移動封包。帶鍵的冷卻會在重複進入通知出現時限制重播,但不會提高掃描頻率。

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 鉤子精簡。初始化後,其時鐘每個伺服器 tick 執行,通常每秒 20 次。沒有此鉤子的腳本仍可能產生每 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) 用於小範圍的區域檢查沒問題,但大範圍掃描應留在外掛執行環境中處理。