跳至主要內容

Lua 腳本:NPC 腳本

webapp_banner.jpg

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.zonescontext.schedulercontext.cooldownscontext.logcontext.eventcontext.player——再加上一個 NPC 專屬的 context.npc 表。凡是 MagmaCore 對腳本公開的東西,在這裡都能使用。

實驗性功能

NPC Lua 腳本仍屬實驗性質。NPC 專屬的鉤子與 context.npc 輔助方法可能會變更。共用的表(context.worldcontext.zonescontext.schedulercontext.cooldownscontext.logcontext.eventcontext.player)與腳本引擎Lua API 參考中記載的是同一套。


檔案位置

在下列位置建立 NPC 腳本檔案:

plugins/
EliteMobs/
npc_scripts/
wave.lua

子資料夾被遞迴掃描。不過腳本是僅以檔名註冊的,所以 npc_scripts/wave.luanpc_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_versionnumber必填。必須為 1
prioritynumber選填。值越低越先執行。
on_spawnfunction在 NPC 生成後執行。
on_removefunction在 NPC 被移除時執行。
on_game_tickfunction只要 NPC 有效,每個伺服器 tick 都會執行。請盡量保持極輕量。
on_npc_interactfunction玩家與該 NPC 互動時執行。
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 鄰近鉤子使用 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_lightningspawn_particleplay_soundset_block_atplace_temporary_blockspawn_entityspawn_fireworkget_nearby_entitiesget_nearby_playersraycast 等等。座標形式(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.schedulerrun_later(ticks, fn)run_repeating(delay, interval, fn)cancel(task_id)
context.cooldowns共用的 MagmaCore 冷卻時間:local_readylocal_remainingcheck_localset_localglobal_readyset_global
context.loginfo(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 鉤子中都可使用。

欄位

欄位型別說明
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 或 locationnil面向一個方向向量,或轉向某個位置/玩家位置。
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 時傳送。

若 NPC 沒有設定巡邏,或請求無法被接受,移動方法會回傳 false。請參閱 NPC 與首領巡邏


context.player

context.player 可在 on_npc_interacton_npc_proximity_enteron_npc_proximity_leave 中使用。在不涉及玩家的生命週期鉤子中,它為 nil

它是共用的 MagmaCore 玩家包裝物件——與 Boss 能力和 FMM 腳本所用的完整生物/玩家表相同,因此公開的內容遠不只基本項目(生命值、藥水效果、send_messageshow_titleshow_action_barget_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.playercontext.npc:get_nearby_players(...) 回傳的包裝物件,以及 FreeMinecraftModels 道具與物品腳本所看到的那些:

欄位型別說明
is_eliteboolean若 EliteMobs 將該實體追蹤為精英,則為 true
is_custom_bossboolean若它是自訂 Boss 則為 true(當 is_elitefalse 時永遠是 false
is_significant_bossboolean對於生命值倍率大於 1 的自訂 Boss 為 true——也就是實務上「這是真正的 Boss,不是增援」的判斷
elitetable 或 nil僅存在於精英身上。見下方

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 能力的實體包裝物件上,那些是由另一套 Boss 端的表建構器所建立——該組欄位請見 Boss 與實體


context.event

當鉤子沒有對應的 Bukkit 事件時,context.eventnil。存在時它是共用的 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, functioninterval tick 執行一次(初始延遲為 0)。回傳一個任務 ID。
cancel(task_id) / cancel_task(task_id)number取消自己擁有的任務。

排程器回呼會收到一個全新的 context。它們不會收到原本的 context.playercontext.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啟動全域冷卻。
Unified cooldown API

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_enteron_npc_proximity_leave,而不是每 tick 輪詢附近玩家。
  • 使用 context.cooldowns:check_local(...) 來節制動畫、音效與粒子爆發。
  • 當某個行為不需要每 tick 執行時,請使用 context.scheduler:run_repeating(...) 並設定合理的間隔。
  • 避免在 Lua 中進行大範圍搜尋。context.npc:get_nearby_players(radius) 用於小範圍的區域檢查沒問題,但大範圍掃描應留在外掛執行環境中處理。

相關頁面