跳至主要內容

Lua 腳本:範例與模式

本頁提供完整的 EliteMobs Lua 能力範例及說明。將每個範例儲存成獨立的 .lua 檔案,附加到測試 Boss;編輯後執行 /em reload,再生成新的 Boss。slam、roar、frenzy 和 transform 等動畫名稱必須存在於 Boss 的自訂模型中,它們不是內建動畫。沒有自訂模型的 Boss 會略過這些動畫呼叫。下文時間均以 20 TPS 計算。

如果你是 Lua 能力的新手,請從入門指南開始。NPC 腳本請參閱 NPC 腳本。完整 Boss 能力 API 詳情請參閱 API 參考、Boss 與實體、世界與環境、區域與目標選擇和列舉。

webapp_banner.jpg


範例:使用腳本工具進行基於區域的目標選擇​

本範例教授: 如何使用 context.script 從 Lua 建立 EliteScript 風格的區域幾何,生成粒子並對區域內的實體造成傷害。

完整能力檔案(點擊展開)
return {
api_version = 1,

on_boss_damaged_by_player = function(context)
local cone = context.script:zone({
shape = "CONE",
Target = {
targetType = "SELF",
offset = "0,1,0"
},
Target2 = {
targetType = "NEARBY_PLAYERS",
range = 20
},
radius = 5
})

context.script:spawn_particles(
cone:full_target(0.4),
{
particle = "FLAME",
amount = 1,
speed = 0.05
}
)

context.script:damage(cone:full_target(), 1.0, 1.5)
end
}

說明​

  1. 區域建立 -- context.script:zone(...) 使用與 EliteScript 區域相同的欄位名稱。Target 將圓錐頂點設在 Boss 上方一格。解析出的第一個 Target2 位置決定底面中心,因此也決定圓錐長度;radius = 5 設定底面半徑。NEARBY_PLAYERS 搜尋沿各軸延伸 20 格的長方體,不保證傳回最近的玩家。在此傷害鉤子中,要瞄準攻擊者可使用 DIRECT_TARGET。找不到玩家就不會建立圓錐;兩個端點必須位於同一世界且位置不同。
  2. 粒子生成 -- cone:full_target(0.4) 傳回目標控制碼,每次呼叫以 40% 機率隨機選取區域中的各個點。不保證點數恰好占 40%,也不會篩選傷害接收者。粒子設定使用 EliteScript 的 particle、amount 和 speed 欄位。粒子工具會將取樣區域位置沿 X、Z 軸各偏移半格,因此粒子並不精確表示實體命中邊界。
  3. 傷害 -- context.script:damage(cone:full_target(), 1.0, 1.5) 命中圓錐內解析出的玩家,因為區域預設篩選器為 PLAYER。第一個數字(1.0)是提交的基礎傷害,第二個(1.5)是對玩家套用的 EliteMobs 傷害倍率。最終傷害由戰鬥處理流程決定。

此範例在每次分派的命中事件中執行,沒有本地或全域冷卻,粒子與傷害之間也沒有預警延遲。與其他攻擊組合時,請明確檢查冷卻。每次靜態區域查詢都會重新解析目標;Lua 區域控制碼不會自動套用 YAML 動作條件或目標快取。

EliteScript 欄位名稱

傳遞給 context.script 的區域和目標表使用 EliteScript 欄位名稱(targetType、shape、Target、Target2、range、offset、coverage)。完整清單請參閱 EliteScript 區域和 EliteScript 目標。


範例:狀態 + 排程器攻擊迴圈​

本範例教授: 使用 context.state 追蹤執行時狀態,context.scheduler 執行重複任務,以及正確的戰鬥進入/退出生命週期。

完整能力檔案(點擊展開)
local function pick_action(context)
local roll = math.random(1, 2)

if roll == 1 then
context.boss:play_model_animation("slam")
else
context.boss:play_model_animation("roar")
end
end

return {
api_version = 1,

on_spawn = function(context)
context.state.started = false
context.state.loop_task_id = nil
end,

on_enter_combat = function(context)
if context.state.started then
return
end

context.state.started = true

context.state.loop_task_id = context.scheduler:run_every(100, function(loop_context)
if loop_context.boss.exists then
pick_action(loop_context)
end
end)
end,

on_exit_combat = function(context)
if context.state.loop_task_id ~= nil then
context.scheduler:cancel_task(context.state.loop_task_id)
context.state.loop_task_id = nil
end
context.state.started = false
end
}

說明​

  1. 狀態初始化 -- on_spawn 將 context.state.started 設為 false,context.state.loop_task_id 設為 nil。state 表在此 Boss 上此能力執行個體的各個鉤子之間保留。其他能力擁有獨立狀態表。
  2. 戰鬥防護 -- on_enter_combat 在啟動迴圈前檢查 context.state.started,防止事件重複觸發時建立多個重疊迴圈。
  3. 排程器模式 -- context.scheduler:run_every(100, callback) 以零延遲安排首次呼叫,之後每 100 tick(5 秒)重複一次,並不會先等待五秒。若需要首次延遲,使用 run_repeating(100, 100, callback)。回呼接收不含觸發事件或玩家的新上下文,因此 loop_context.boss 是新快照。將傳回的數字任務 ID 儲存到狀態中。此例只選擇動畫,本身不造成傷害。
  4. 退出時清理 -- on_exit_combat 使用儲存的任務 ID 取消重複任務並重設狀態。不清理的話,排程器會在戰鬥結束後繼續執行。
始終取消排程器任務

在 on_exit_combat 中取消僅限戰鬥的重複任務。否則戰鬥結束後它們仍會被排程,直到被取消或能力執行個體關閉。暫停的執行個體會略過回呼但保留任務。未捕捉的回呼錯誤會關閉執行個體並取消其擁有的任務。


範例:命中時的火焰效果​

本範例教授: 簡單的「命中時套用效果」能力,這是常見的戰鬥能力模式。

完整能力檔案(點擊展開)
return {
api_version = 1,

on_player_damaged_by_boss = function(context)
-- Guard: the player may be nil in edge cases
if context.player == nil then
return
end

if not context.cooldowns:global_ready() then
return
end

-- Check and set a 60-tick (3 second) local cooldown in one call
if not context.cooldowns:check_local("fire_touch", 60) then
return
end

-- Coordinate with other powers that check the shared cooldown
context.cooldowns:set_global(40)

-- Set the player on fire for 60 ticks (3 seconds)
context.player:set_fire_ticks(60)

-- Visual feedback: spawn flame particles at the player's location
context.world:spawn_particle_at_location(
context.player.current_location,
{ particle = "FLAME", amount = 20, speed = 0.1 }
)

-- Tell the player what happened
context.player:send_message("&cThe boss's touch burns!")
end
}

說明​

  1. Nil 防護 -- context.player 來自目前事件的玩家參與者或直接目標。使用前務必檢查,尤其是將程式碼改用於其他鉤子時。排程器回呼不會繼承此玩家。
  2. 本地冷卻 -- 檢查 global_ready() 後,context.cooldowns:check_local("fire_touch", 60) 檢查該鍵,可用時立即設定 60 tick 冷卻;否則傳回 false 並離開鉤子。此鍵由同一 Boss 上的能力共用,不同 Boss 相互獨立。組合範例時請加上能力名稱前綴。
  3. 燃燒時間 -- context.player:set_fire_ticks(60) 先檢查玩家是否有效、存活、在線上、與來源位於同一世界,以及在副本實例內是否為目前參與者,再將 Bukkit 燃燒時間設為 60 tick(3 秒)。它會取代既有時間,而不是增加 60 tick。其他遊戲機制可能滅火;訊息和粒子不能證明效果已獲准套用。
  4. 粒子 -- context.world:spawn_particle_at_location(location, spec) 在特定位置生成粒子。規格表接受 particle(Bukkit 粒子列舉名稱)、amount 與 speed。
  5. 訊息 -- context.player:send_message(text) 會送出帶顏色代碼的聊天訊息。像 &c(紅色)這類標準 Minecraft 顏色代碼會自動生效。
  6. 全域冷卻 -- context.cooldowns:set_global(40) 在套用效果前將 Boss 的共用冷卻設為 40 tick(2 秒)。此例先檢查它,再消耗本地冷卻。其他 Lua 鉤子和回呼若也應等待,必須呼叫 global_ready();設定全域冷卻不會暫停所有 Lua 回呼。

範例:使用原生 Lua 區域的基於區域的 AoE 能力​

本範例教授: 建立及查詢原生 Lua 區域,對區域內的玩家造成傷害。

完整能力檔案(點擊展開)
return {
api_version = 1,

on_spawn = function(context)
context.state.aoe_task_id = nil
end,

on_enter_combat = function(context)
-- Prevent duplicate loops
if context.state.aoe_task_id ~= nil then
return
end

context.state.aoe_task_id = context.scheduler:run_every(60, function(tick_context)
-- Make sure the boss is still alive
if not tick_context.boss.exists then
return
end

if not tick_context.cooldowns:global_ready() then
return
end

-- Check and set this boss's named cooldown before applying effects
if not tick_context.cooldowns:check_local("pulse_aoe", 60) then
return
end
tick_context.cooldowns:set_global(60)

-- Build a sphere zone centered on the boss's current position
local zone_def = {
kind = "sphere",
radius = 8,
origin = tick_context.boss:get_location()
}

-- Find all players inside the sphere
local victims = tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })

-- Damage and show particles on each player found
for i = 1, #victims do
local victim = victims[i]
victim:deal_custom_damage(4.0)

tick_context.world:spawn_particle_at_location(
victim.current_location,
{ particle = "DUST", amount = 15, speed = 0, red = 128, green = 0, blue = 255 }
)
end

-- Spawn a particle burst at the boss; this does not draw the sphere
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "WITCH", amount = 40, speed = 0.1 }
)
end)
end,

on_exit_combat = function(context)
if context.state.aoe_task_id ~= nil then
context.scheduler:cancel_task(context.state.aoe_task_id)
context.state.aoe_task_id = nil
end
end
}

說明​

  1. 狀態設定 -- on_spawn 把 aoe_task_id 初始化為 nil。這個任務 ID 會保存重複排程的參考。
  2. 重複攻擊 -- on_enter_combat 啟動首次延遲為零、之後每 60 tick(3 秒)重複的任務。檢查任務 ID 可避免重複迴圈。每次回呼先檢查全域冷卻,再檢查並設定 pulse_aoe,然後在效果前設定 60 tick 全域冷卻。這僅與檢查這些鍵的能力協調,不會自動阻止所有腳本。
  3. 區域定義 -- zone_def 表使用原生 Lua 區域語法。kind 欄位指定形狀("sphere"),radius 設定大小,origin 則設為回呼執行當下 Boss 的位置。這表示該區域會隨 Boss 移動而跟隨。
  4. 查詢實體 -- tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" }) 根據形狀與實體的相交判定傳回從 1 開始的 Lua 陣列,而非精確檢查腳部位置距離。查詢搜尋 Boss 目前世界,不按玩家遊戲模式或遭遇參與情況篩選。篩選器接受 "players"、"elites"、"mobs" 或 "living"(預設),並排除 Boss 自身。
  5. 造成傷害 -- victim:deal_custom_damage(4.0) 透過 BossCustomAttackDamage 提交歸屬於 Boss 的 4 點基礎傷害。它略過無敵、死亡的目標及非生存或冒險模式玩家。在精英攻擊玩家的路徑上,它繞過正常傷害公式及格擋減傷,並清除原版護甲減傷。傷害監聽器、取消事件及傷害吸收仍可改變實際生命損失。工具在提交後重設目標的原生傷害冷卻;它與 context.script:damage 使用不同路徑。
  6. 粒子回饋 -- 即使傷害被拒絕,仍會在每個查詢到的玩家儲存的位置顯示紫色塵埃。Boss 位置的 WITCH 粒子只是一次粒子噴發,不會描繪圓環或球體邊界。這些簡化粒子表預設位置散布為零,塵埃大小固定為 1。
  7. 清理 -- on_exit_combat 會取消該重複任務並清除狀態,做法與前一個範例相同。
原生區域 vs. 腳本工具區域

此範例使用原生 Lua 區域(context.zones:get_entities_in_zone()),接受包含 kind、radius、origin 等欄位的簡單表。腳本工具(context.script:zone(...))使用 EliteScript 欄位名稱,如 shape、Target 和 Target2。簡單形狀使用原生區域,需要進階目標解析時使用 context.script。


範例:多階段 Boss 機制​

本範例教授: 使用狀態追蹤 Boss 階段,並在生命值門檻處切換行為。

這些是 Lua 狀態階段,不是會替換 Boss 定義的 EliteMobs YAML phases:。此處不會替換或治療 Boss。之後回血也不會返回階段 1;離開戰鬥仍保留階段 2,直到能力執行個體重新建立。

完整能力檔案(點擊展開)
local function phase_one_attack(context)
-- One damage pulse per callback; the animation does not delay it
context.boss:play_model_animation("slam")

local zone_def = {
kind = "sphere",
radius = 5,
origin = context.boss:get_location()
}

local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(3.0)
end

context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "EXPLOSION", amount = 3, speed = 0 }
)
end

local function phase_two_attack(context)
-- One pulse per callback, repeated more frequently in phase 2
context.boss:play_model_animation("frenzy")

local zone_def = {
kind = "sphere",
radius = 8,
origin = context.boss:get_location()
}

local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(2.0)
targets[i]:apply_potion_effect("SLOWNESS", 40, 1)
end

context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "DUST", amount = 30, speed = 0.2, red = 255, green = 0, blue = 0 }
)

context.world:play_sound_at_location(
context.boss:get_location(),
"entity.wither.ambient",
1.0, 1.5
)
end

local function start_attack_loop(context)
if context.state.attack_task_id ~= nil then return end
local phase = context.state.phase or 1
local interval = phase == 2 and 40 or 100
local attack = phase == 2 and phase_two_attack or phase_one_attack
context.state.attack_task_id = context.scheduler:run_every(interval, function(tick_context)
if not tick_context.boss.exists or not tick_context.boss.is_in_combat then return end
if tick_context.cooldowns:check_local("phase_attack", interval) then
attack(tick_context)
end
end)
end

return {
api_version = 1,

on_spawn = function(context)
context.state.phase = 1
context.state.attack_task_id = nil
end,

on_enter_combat = function(context)
start_attack_loop(context)
end,

on_game_tick = function(context)
-- Only check phase transition every 20 ticks (1 second) to stay lightweight
if not context.boss.is_in_combat then return end
if not context.cooldowns:check_local("phase_check", 20) then
return
end

-- Skip if already in phase 2
if context.state.phase ~= 1 then
return
end

-- Check health ratio
local health_ratio = context.boss.health / context.boss.maximum_health

if health_ratio <= 0.5 then
-- Transition to phase 2
context.state.phase = 2

context.log:info("Boss entering phase 2 at " .. tostring(math.floor(health_ratio * 100)) .. "% health")

-- Cancel the old attack loop
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end

-- Play transition effects
context.boss:play_model_animation("transform")

-- Announce the phase change to nearby players
local nearby = context.players.nearby_players(40)
for i = 1, #nearby do
nearby[i]:send_message("&4&lThe boss enters a frenzy!")
nearby[i]:show_title("&4Phase 2", "&cThe boss is enraged!", 10, 40, 10)
end

start_attack_loop(context)
end
end,

on_exit_combat = function(context)
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end
end
}

說明​

  1. 狀態初始化 -- on_spawn 將 phase 設為 1,attack_task_id 設為 nil。這些狀態屬於此能力執行個體,不與其他能力共用,也不會跨重新載入儲存。
  2. 攻擊迴圈 -- on_enter_combat 透過 start_attack_loop 恢復目前階段:階段 1 間隔為 100 tick,階段 2 為 40。兩者首次回呼皆為零延遲。每次攻擊仍須等待 phase_attack 冷卻結束;切換階段不會清除舊冷卻,因此某次回呼可能略過攻擊。此例不檢查全域冷卻。階段 1 向半徑 5 格球體內的玩家提交 3 點自訂傷害,並播放動畫及粒子,不會等待動畫時長。
  3. on_game_tick 中的階段檢查 -- on_game_tick 在非戰鬥狀態下立即返回,然後檢查20 tick的冷卻("phase_check")。若 Boss 已進入階段2,也會提早返回。
  4. 生命值門檻 -- context.boss.health / context.boss.maximum_health 會給出目前生命值的比例。當它降到 50% 或以下時,轉換就會開始。
  5. 階段轉換 -- 舊的攻擊迴圈會被取消、播放變身動畫、附近玩家會收到訊息與標題,並啟動一個更快的新攻擊迴圈(每 40 tick 而非 100 tick)。
  6. 第 2 階段攻擊 -- phase_two_attack 使用半徑 8 格的球體,每次成功回呼向每個查詢到的玩家提交一次 2 點傷害。它嘗試施加 40 tick 的緩速 II(amplifier = 1),受上述狀態效果參與檢查限制,然後顯示紅色塵埃並播放凋零音效。它不會在一次回呼中安排多次命中。
  7. 記錄 -- context.log:info(...) 會寫入伺服器主控台,這在開發期間除錯階段轉換時非常寶貴。
  8. 清理 -- on_exit_combat 會取消目前作用中的任何攻擊迴圈,無論 Boss 處於哪個階段。
保持 on_game_tick 輕量

活動且未暫停的執行個體每個伺服器 tick 接收一次 on_game_tick(20 TPS 時每秒 20 次)。請用冷卻限制高負荷操作,例如 check_local("phase_check", 20)。這是定期檢查生命值,致命一擊可能在階段檢查前移除 Boss。超出執行預算會關閉該能力執行個體並取消其任務。


AI 生成提示​

要讓 AI 可靠地生成 Lua 能力,請在提示中包含:

  • 精確的鉤子名稱 -- 如 on_player_damaged_by_boss,而非「當 Boss 擊中玩家時」。
  • 原生 Lua 區域或腳本工具 -- 明確選擇。原生區域使用 context.zones:get_entities_in_zone(zone_def, opts),欄位為 kind、radius、origin。腳本工具使用 context.script:zone(...),欄位為 EliteScript 的 shape、Target、Target2 等。
  • 本地與全域冷卻 -- 指定含能力名稱前綴的鍵及 tick 時長。context.cooldowns:check_local(key, ticks) 檢查並設定 Boss 的命名冷卻。要與全域冷卻協調,必須在消耗本地鍵前明確檢查 global_ready(),並在效果前設定全域時長。
  • 自訂模型動畫名稱 -- 如 context.boss:play_model_animation("slam")。AI 無法猜出模型的動畫名稱,請明確提供。
  • 目標選擇 -- 指定使用 context.player(事件玩家)、context.players.nearby_players(range) 或區域查詢。
  • 效果類型 -- 傷害(deal_custom_damage)、藥水(apply_potion_effect)、燃燒(set_fire_ticks)、速度(set_velocity_vector、apply_push_vector)等。
  • 使用正確包裝器的方法名稱 -- 提供適用的 Boss 或共用 API 參考,不要自行發明別名。
  • 腳本工具使用 EliteScript 欄位名稱 -- 使用 targetType、shape、Target、Target2、range、offset、coverage,而非 Lua 風格名稱。
  • 排程器時間與上下文 -- 指定首次回呼是立即執行或延遲執行。run_every 首次延遲為零,run_repeating 接受獨立的延遲參數。使用回呼的新上下文,它不含觸發玩家或事件。後續需要玩家 UUID 或位置時,請明確儲存。

良好的提示範例​

編寫一個 Lua 能力,在 on_enter_combat 中啟動首次延遲 80 tick、間隔 80 tick 的重複任務。每次回呼建立以 Boss 為中心、半徑 6 的原生 Lua 球體,查詢玩家並向每人提交 2.0 自訂傷害。先檢查全域冷卻,再以 80 tick 檢查並設定本地鍵 "my_power:pulse",然後在效果前將全域冷卻設為 80。在 on_exit_combat 中取消任務。在每個查詢到的玩家位置生成 DUST 粒子(red=0、green=255、blue=100)。

應額外納入的限制​

  • 「傳回單一個包含 api_version = 1 的表。」
  • 「在 on_spawn 中初始化所有狀態欄位。」
  • 「以 if not tick_context.boss.exists then return end 保護排程回呼。」
  • 「在 on_exit_combat 中取消所有重複任務。」
  • 「不要自行發明方法名稱 -- 只使用 API 參考頁面上的方法。」
  • 「使用 context.cooldowns:check_local(key, ticks) 進行合併的檢查與設定。」

QC 檢查清單(供人工或 AI 審查)​

部署 Lua 能力之前,請檢查:

  1. 檔案傳回恰好一個包含 api_version = 1 的表。
  2. 鉤子名稱與鉤子列表精確相符,例如 on_player_damaged_by_boss,而非 on_player_hit。
  3. 在可能沒有玩家的鉤子中,使用 context.player 前用 == nil 檢查。
  4. context.state 欄位在 on_spawn 中初始化。
  5. 每個僅限戰鬥的重複任務,不論透過 run_every 或 run_repeating 建立,都在 on_exit_combat 中取消,且首次延遲符合意圖。
  6. 排程器回呼使用自己的上下文,不假設存在觸發玩家或事件。
  7. 冷卻鍵包含能力名稱前綴,時長以 tick 為單位。全域協調包含明確的可用性檢查。
  8. on_game_tick 鉤子用冷卻檢查保護重邏輯。
  9. 所有方法名稱存在於 API 參考中,沒有自行發明的別名。
  10. 腳本工具表使用 EliteScript 欄位名稱(targetType、shape、Target 等),而非 Lua 風格名稱。
  11. 原生區域定義使用 kind、radius、origin、destination 等。
  12. 能力不在鉤子或回呼中呼叫阻塞或耗時操作。
  13. 粒子設定使用有效的 Bukkit 粒子列舉名稱(UPPER_CASE),例如 "FLAME"、"DUST"、"EXPLOSION"。
  14. 重新載入修改後的檔案,生成新的 Boss,在測試伺服器上檢查可見行為及主控台輸出。檔案解析成功不能證明攻擊正常執行。

最佳實踐​

  • 從小鉤子開始並驗證。 寫一個發送日誌訊息的 on_spawn。確認它觸發後再繼續建構。

  • 保持輔助函式為區域性。 在傳回表上方宣告 local function pick_action(context) 之類的輔助函式,讓相依關係明確。每個能力實例本來就擁有獨立的 Lua 執行個體。

  • 將幾何放入腳本工具。 如果需要錐體、旋轉射線、平移射線或動畫區域,使用帶有 EliteScript 欄位名稱的 context.script:zone(...)。腳本工具重用 EliteScript 區域引擎。

  • 使用 context.state 儲存執行狀態。 它屬於一個 Boss 上的一個能力執行個體,並在該執行個體的鉤子之間保留。不與其他能力共用,也不跨重新載入儲存。

  • 使用命名的本地冷卻鍵。 使用 "fire_touch" 或 "aoe_pulse" 等描述性鍵,而非簡單數字。明確指定的鍵由同一 Boss 的所有能力共用;加上能力名稱前綴,如 "my_power:fire_touch",以防衝突。

  • 保持 on_game_tick 輕量。 用冷卻限制高負荷操作,並為執行預算保留充足餘裕。

  • 完成時取消重複任務。 僅用於戰鬥的迴圈應在 on_exit_combat 中取消。執行個體關閉會自動取消其擁有的排程器任務,但戰鬥結束本身不會取消它們。

  • 在排程器回呼中使用新上下文。 run_every、run_repeating 和 run_after 回呼收到的新上下文不含排程它們的事件或玩家。使用此參數,並明確儲存所需 UUID 或位置;外部上下文可能含過時快照。

  • 用 context.log:info() 記錄狀態轉換。 開發時記錄階段切換、冷卻開始及排程器啟停。部署前刪除頻繁日誌:Boss 的 context.log:debug() 也以 info 層級寫入,只是前綴不同。

  • 重用相關 EliteScript 參考。 區域、目標及相對向量介紹共用解析器。獨立 Lua 工具不會自動評估 YAML 條件、等待或動作層級快取;請在 Lua 中編寫檢查。繼承目標及落地位置需要 YAML 動作上下文,而這些控制碼不具備該上下文。


常見初學者錯誤​

  • 在排程器回呼中使用外部 context。 外部上下文擷取了鉤子執行時的快照。在 run_every 或 run_after 回呼內,始終使用回呼自身的參數(例如 tick_context),它會給你一個新鮮的快照。

  • 忘記取消重複任務。 如果你在 on_enter_combat 中啟動了 run_every 但從未取消,任務會一直執行直到 Boss 從伺服器移除,即使戰鬥已結束。

  • 不在 on_spawn 中初始化狀態。 如果你在 on_game_tick 中讀取 context.state.phase 但從未在 on_spawn 中設定它,它將是 nil,你的比較將表現異常。

  • 不檢查 nil 就使用 context.player。 確認哪些鉤子會提供玩家。排程器回呼預設沒有玩家,缺少檢查可能關閉能力執行個體。

  • 在腳本工具呼叫中使用 Lua 風格欄位名稱。 使用文件中的 EliteScript 欄位,如 targetType、Target、Target2 和 shape。不支援 target_type、zone_shape 等底線變體。區域解析器接受小寫 target 和 target2,但這不代表任意 Lua 風格別名都有效。

  • 在 on_game_tick 中不加冷卻就執行重邏輯。 多個 Boss 上的全世界實體查詢及密集粒子幾何負荷很高。限制這些工作的頻率,並讓每個回呼保持在執行預算內。

  • 混用包裝器 API。 EliteMobs 的 Boss 專用包裝器使用 entity:teleport_to_location(loc) 和 player:set_velocity_vector(vec)。共用 MagmaCore 包裝器還有其他別名。從其他外掛範例借用方法前,先檢查目前上下文的參考文件。

  • 使用 context.boss.health 設定生命值。 context.boss.health 是唯讀快照。要治癒 Boss,使用 context.boss:restore_health(amount)。

  • 忘記 api_version = 1。 返回的表必須包含此欄位,否則 EliteMobs 不會載入該能力。


後續步驟​