Class NPCScriptManager


public class NPCScriptManager extends AbstractScriptManager
NPC 對話腳本的生命週期管理器,採單例(instance)。

承襲 AbstractScriptManager,負責解析並執行 scripts/npc/<npcId>.js(任務則 quest/、 特殊則 special/):start(MapleClient, int) 取得 Invocable GraalJS 引擎、建立 NPCConversationManager 並以全域 cm 注入腳本後呼叫 start()action(MapleClient, byte, byte, int) 轉送後續 action(mode,type,selection) 互動;dispose(MapleClient) 結束對話並清快取。

每個 MapleClient 的進行中對話記錄於 cmsWeakHashMap),並以 c.getNPCLock() 串行化同一連線的腳本執行。除 NPC 外,亦透過 startQuest(MapleClient, int, int)endQuest(MapleClient, int, int, boolean) 驅動 MapleQuest 腳本,並經 runCommand(MapleClient, String, String, String[]) 以同一架構執行 scripts/command/{gm,player}/ 指令腳本,以及經 runItemScript(MapleClient, String, int, int, int) 執行 scripts/item/{consume,cash,scroll}/ 道具觸發腳本。

  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    final void
    action(MapleClient c, byte mode, byte type, int selection)
    轉送 NPC 對話的後續互動,呼叫腳本的 action(mode, type, selection) 函式。
    final void
    結束此連線目前的對話會話,並清除對應的 per-client 腳本引擎快取。
    final void
    endQuest(MapleClient c, byte mode, byte type, int selection)
    轉送任務完成對話的後續互動,呼叫任務腳本的 end(mode, type, selection)
    final void
    endQuest(MapleClient c, int npc, int quest, boolean customEnd)
    啟動任務的「完成」腳本(scripts/quest/<questId>.js)並呼叫其 end()
    取得指定連線目前進行中的對話會話管理器。
    static final NPCScriptManager
    取得唯一的單例實例。
    final boolean
    對應指令腳本「檔案是否存在」(供 CommandProcessor 決定是否覆寫內建 Java 指令)。
    final boolean
    hasItemScript(String folder, int itemId)
    對應道具腳本「檔案是否存在」(供 InventoryHandler 決定是否覆寫內建 Java 道具行為)。
    final void
    執行地圖的「首次進入時」腳本(scripts/map/onFirstUserEnter/<script>.js)。
    final void
    執行地圖的「進入時」腳本(scripts/map/onUserEnter/<script>.js)。
    final boolean
    runCommand(MapleClient c, String folder, String name, String[] splitted)
    以「NPC 腳本架構」執行一支指令腳本:載入 → 建 NPCConversationManager → 注入 cm/player/commands → 呼叫 start()
    final boolean
    runItemScript(MapleClient c, String folder, int itemId, int slot, int equipSlot)
    以「NPC 腳本架構」執行一支道具觸發腳本(不注入額外全域)。
    final boolean
    runItemScript(MapleClient c, String folder, int itemId, int slot, int equipSlot, Map<String,Object> extraBindings)
    以「NPC 腳本架構」執行一支道具觸發腳本:載入 → 建 NPCConversationManager → 注入 cmplayeritemIdslotequipSlot(及 extraBindings 內的額外全域)→ 呼叫 start()
    final void
    start(MapleClient c, int npc)
    啟動指定 NPC 的對話腳本(不指定型號 / 自訂腳本)。
    final void
    start(MapleClient c, int npc, int mode)
    以指定型號啟動 NPC 對話腳本。
    final void
    start(MapleClient c, int npc, int mode, String script)
    啟動 NPC 對話腳本的核心實作:解析腳本路徑、建立會話、注入全域 cm 並呼叫 start()
    final void
    start(MapleClient c, int npc, String script)
    以自訂腳本名啟動對話(走 scripts/special/ 路徑)。
    final void
    startQuest(MapleClient c, byte mode, byte type, int selection)
    轉送任務開始對話的後續互動,呼叫任務腳本的 start(mode, type, selection)
    final void
    startQuest(MapleClient c, int npc, int quest)
    啟動任務腳本(scripts/quest/<questId>.js)並呼叫其 start()

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • NPCScriptManager

      public NPCScriptManager()
  • Method Details

    • getInstance

      public static final NPCScriptManager getInstance()
      取得唯一的單例實例。
      Returns:
      全域共用的 NPCScriptManagerinstance
    • start

      public final void start(MapleClient c, int npc)
      啟動指定 NPC 的對話腳本(不指定型號 / 自訂腳本)。

      轉呼 start(MapleClient, int, String) 並以 null 腳本名走預設 scripts/npc/<npcId>.js 路徑。

      Parameters:
      c - 觸發對話的客戶端連線
      npc - 目標 NPC 的 id
    • start

      public final void start(MapleClient c, int npc, int mode)
      以指定型號啟動 NPC 對話腳本。

      轉呼 start(MapleClient, int, int, String)mode != 0 時改載入 scripts/npc/<npcId>_<mode>.js 變體。

      Parameters:
      c - 觸發對話的客戶端連線
      npc - 目標 NPC 的 id
      mode - 對話腳本型號(0 表預設、不附加後綴)
    • start

      public final void start(MapleClient c, int npc, String script)
      以自訂腳本名啟動對話(走 scripts/special/ 路徑)。

      轉呼 start(MapleClient, int, int, String),型號固定為 0。當 scriptnull 時改載入 scripts/special/<script>.js 而非預設 NPC 腳本。

      Parameters:
      c - 觸發對話的客戶端連線
      npc - 對話框講者的 NPC id
      script - special 資料夾下的腳本名(可為 null 表走預設 NPC 路徑)
    • start

      public final void start(MapleClient c, int npc, int mode, String script)
      啟動 NPC 對話腳本的核心實作:解析腳本路徑、建立會話、注入全域 cm 並呼叫 start()

      流程要點:

      • c.getNPCLock() 鎖內執行,串行化同一連線的腳本;全程以 try/finally 確保解鎖。
      • 先驗證 NPC 存在(MISSINGNO 視為不存在)且未被 GameConstants.isBlockedNpc(int) 封鎖,否則 dispose(MapleClient) 後返回。
      • 腳本路徑由參數決定:script != nullspecial/<script>.js;否則 mode != 0npc/<npc>_<mode>.js,再否則 npc/<npc>.js;找不到時退回 special/notcoded.js
      • 僅在 cms 尚無此連線的進行中對話且 c.canClickNPC()(點擊節流)通過時才開始。

      副作用:建立 NPCConversationManager 存入 cms、將其以全域 cm 注入引擎、把玩家 對話狀態設為 1setConversation)並標記點擊時間(setClickedNPC);GM 會額外收到提示訊息。 任何例外都會被記錄(log + ScriptEx_Log 檔)並 dispose(MapleClient) 收場。

      Parameters:
      c - 觸發對話的客戶端連線
      npc - 目標 NPC 的 id
      mode - 對話腳本型號(0 表預設、不附加後綴)
      script - special 資料夾下的腳本名(可為 null 表走預設 NPC 路徑)
    • action

      public final void action(MapleClient c, byte mode, byte type, int selection)
      轉送 NPC 對話的後續互動,呼叫腳本的 action(mode, type, selection) 函式。

      mode == -1(玩家關閉對話框)時直接忽略不處理。否則取出 cms 中此連線的進行中 NPCConversationManager;若無會話或其 getLastMsg() > -1(尚有待回應的訊息框)則返回。

      c.getNPCLock() 鎖內執行(try/finally 確保解鎖):若會話已標記 pendingDisposaldispose(MapleClient),否則更新點擊時間並 invokeFunction("action", …)。腳本錯誤會記錄並 dispose(MapleClient);GM 玩家會額外收到錯誤提示訊息。

      Parameters:
      c - 進行對話的客戶端連線
      mode - 玩家操作模式(-1 表關閉對話、不處理)
      type - 對話框類型
      selection - 玩家於該對話框的選擇值
    • hasCommandScript

      public final boolean hasCommandScript(String folder, String name)
      對應指令腳本「檔案是否存在」(供 CommandProcessor 決定是否覆寫內建 Java 指令)。 只查檔、不建引擎。
      Parameters:
      folder - "gm""player"
      name - 不含前綴的指令名(來自玩家輸入)
      Returns:
      檔案存在且名稱合法時為 true
    • runCommand

      public final boolean runCommand(MapleClient c, String folder, String name, String[] splitted)
      以「NPC 腳本架構」執行一支指令腳本:載入 → 建 NPCConversationManager → 注入 cm/player/commands → 呼叫 start()。簡易指令(start() 未開對話框)會自動 dispose;有開對話框者保留,後續由 NPCHandler → action() 續流程 (與 NPC 對話完全相同)。
      Parameters:
      folder - "gm""player"
      name - 不含前綴的指令名
      splitted - 以空白切開的指令與參數(splitted[0] 含前綴),注入為全域 commands
      Returns:
      已實際開始執行(含腳本內部錯誤)為 true;玩家正在對話中 / 找不到檔 / 名稱非法則 false
    • hasItemScript

      public final boolean hasItemScript(String folder, int itemId)
      對應道具腳本「檔案是否存在」(供 InventoryHandler 決定是否覆寫內建 Java 道具行為)。 只查檔、不建引擎。
      Parameters:
      folder - "consume"(消耗品)/"cash"(現金道具)/"scroll"(裝備卷軸)
      itemId - 道具 id(亦即腳本檔名)
      Returns:
      對應腳本檔存在時為 true
    • runItemScript

      public final boolean runItemScript(MapleClient c, String folder, int itemId, int slot, int equipSlot)
      以「NPC 腳本架構」執行一支道具觸發腳本(不注入額外全域)。直接轉呼 runItemScript(MapleClient, String, int, int, int, Map) 並將 extraBindingsnull
      Parameters:
      c - 使用道具的客戶端連線
      folder - "consume""cash""scroll"
      itemId - 道具 id(亦即腳本檔名)
      slot - 道具所在背包欄位
      equipSlot - 卷軸目標裝備欄位(非卷軸傳 -1
      Returns:
      已實際開始執行(含腳本內部錯誤)為 true;玩家正在對話中 / 找不到檔則 false
    • runItemScript

      public final boolean runItemScript(MapleClient c, String folder, int itemId, int slot, int equipSlot, Map<String,Object> extraBindings)
      以「NPC 腳本架構」執行一支道具觸發腳本:載入 → 建 NPCConversationManager → 注入 cmplayeritemIdslotequipSlot(及 extraBindings 內的額外全域)→ 呼叫 start()

      覆寫模型:腳本完全接管該道具,自行套用效果並自行扣除(內建 Java 行為已被呼叫端略過)。未開對話框的 簡易腳本會自動 dispose(MapleClient) 並補送 CWvsContext.enableActions() 解除使用道具的 動作鎖;有開對話框者保留,後續由 NPCHandler → action() 續流程(對話封包本身已解鎖 UI)。

      序列化僅靠 cms(一次一段對話)+ NPC 鎖;玩家正在對話中(cms 佔用)時不執行並回 false

      Parameters:
      c - 使用道具的客戶端連線
      folder - "consume""cash""scroll"
      itemId - 道具 id(亦即腳本檔名)
      slot - 道具所在背包欄位
      equipSlot - 卷軸目標裝備欄位(非卷軸傳 -1
      extraBindings - 額外注入引擎的全域(鍵=全域變數名、值=其值);null 代表無。卷軸腳本由此帶入 equip(目標裝備 Equip)/神匠之魂祝福卷軸
      Returns:
      已實際開始執行(含腳本內部錯誤)為 true;玩家正在對話中 / 找不到檔則 false
    • startQuest

      public final void startQuest(MapleClient c, int npc, int quest)
      啟動任務腳本(scripts/quest/<questId>.js)並呼叫其 start()

      先檢查任務可開始(MapleQuest.canStart(MapleCharacter, Integer))或屬於 GameConstants.accscriptquest(int) 例外清單,否則直接返回。於 c.getNPCLock() 鎖內執行(try/finally 確保解鎖),僅在 cms 無進行中對話且 c.canClickNPC() 時開始。

      副作用:建立型別為 0(start)的 NPCConversationManager 存入 cms、以全域 qm 注入引擎、設玩家對話狀態並標記點擊時間。找不到腳本時對玩家提示並 dispose(MapleClient);GM 另收提示。 例外會記錄並 dispose(MapleClient)

      Parameters:
      c - 觸發任務的客戶端連線
      npc - 對話框講者的 NPC id
      quest - 任務 id
    • startQuest

      public final void startQuest(MapleClient c, byte mode, byte type, int selection)
      轉送任務開始對話的後續互動,呼叫任務腳本的 start(mode, type, selection)

      取出 cms 中此連線的進行中 NPCConversationManager;若無會話或其 getLastMsg() > -1(尚有待回應訊息框)則返回。於 c.getNPCLock() 鎖內執行 (try/finally 確保解鎖):pendingDisposaldispose(MapleClient),否則更新點擊時間並 invokeFunction("start", …)。例外會記錄並 dispose(MapleClient);GM 另收提示。

      Parameters:
      c - 進行任務對話的客戶端連線
      mode - 玩家操作模式
      type - 對話框類型
      selection - 玩家於該對話框的選擇值
    • endQuest

      public final void endQuest(MapleClient c, int npc, int quest, boolean customEnd)
      啟動任務的「完成」腳本(scripts/quest/<questId>.js)並呼叫其 end()

      customEnd 時先檢查 MapleQuest.canComplete(MapleCharacter, Integer),不可完成則返回。針對任務 52403 會強制改用 NPC 9330203。於 c.getNPCLock() 鎖內執行(try/finally 確保解鎖),僅在 cms 無進行中對話且 c.canClickNPC() 時開始。

      副作用:建立型別為 1(end)的 NPCConversationManager 存入 cms、以全域 qm 注入引擎、設玩家對話狀態並標記點擊時間。找不到腳本即 dispose(MapleClient);例外會記錄並 dispose(MapleClient);GM 另收提示。

      Parameters:
      c - 觸發任務完成的客戶端連線
      npc - 對話框講者的 NPC id(任務 52403 會被改寫為 9330203
      quest - 任務 id
      customEnd - true 時略過 canComplete 檢查(由腳本自行控管完成條件)
    • endQuest

      public final void endQuest(MapleClient c, byte mode, byte type, int selection)
      轉送任務完成對話的後續互動,呼叫任務腳本的 end(mode, type, selection)

      取出 cms 中此連線的進行中 NPCConversationManager;若無會話或其 getLastMsg() > -1(尚有待回應訊息框)則返回。於 c.getNPCLock() 鎖內執行 (try/finally 確保解鎖):pendingDisposaldispose(MapleClient),否則更新點擊時間並 invokeFunction("end", …)。例外會記錄並 dispose(MapleClient);GM 另收提示。

      Parameters:
      c - 進行任務對話的客戶端連線
      mode - 玩家操作模式
      type - 對話框類型
      selection - 玩家於該對話框的選擇值
    • onUserEnter

      public final void onUserEnter(MapleClient c, String script)
      執行地圖的「進入時」腳本(scripts/map/onUserEnter/<script>.js)。

      c.getNPCLock() 鎖內執行(try/finally 確保解鎖),僅在 cms 無進行中對話時開始; 找不到腳本時退回 map/onUserEnter/notcoded.js。會建立型別為 2NPCConversationManager 存入 cms、以全域 ms 注入引擎,並呼叫 start() (無此函式則改呼 action(1,0,0))。

      副作用:設玩家對話狀態並標記點擊時間;開啟 isShowInfo 的玩家會看到診斷訊息。例外(ScriptExceptionNoSuchMethodException)會記錄到 ScriptEx_Logdispose(MapleClient)

      Parameters:
      c - 進入地圖的客戶端連線
      script - map/onUserEnter/ 下的腳本名
    • onFirstUserEnter

      public final void onFirstUserEnter(MapleClient c, String script)
      執行地圖的「首次進入時」腳本(scripts/map/onFirstUserEnter/<script>.js)。

      c.getNPCLock() 鎖內執行(try/finally 確保解鎖),僅在 cms 無進行中對話時開始; 找不到腳本時記錄一行 log 並退回 map/onFirstUserEnter/notcoded.js。會建立型別為 3NPCConversationManager 存入 cms、以全域 ms 注入引擎,並呼叫 start() (無此函式則改呼 action(1,0,0))。

      副作用:設玩家對話狀態並標記點擊時間;開啟診斷訊息的玩家會看到提示。例外(ScriptExceptionNoSuchMethodException)會記錄到 ScriptEx_Logdispose(MapleClient)

      Parameters:
      c - 首次進入地圖的客戶端連線
      script - map/onFirstUserEnter/ 下的腳本名
    • dispose

      public final void dispose(MapleClient c)
      結束此連線目前的對話會話,並清除對應的 per-client 腳本引擎快取。

      cms 移除該連線的 NPCConversationManager,再依其 型別 移除相對應的快取引擎:

      • -1:NPC / special(含 _mode 變體與 notcoded
      • 01:任務 start / end 腳本
      • 2map/onUserEnter3map/onFirstUserEnter(各含 notcoded
      • 4:指令腳本(清 per-client 引擎,使下次使用 auto-fresh 重讀檔案)
      • 5:道具觸發腳本(清 per-client 引擎,使下次使用 auto-fresh 重讀檔案)

      最後若玩家仍在對話中(getConversation() == 1)則將其重設為 0。對未持有對話的連線呼叫為安全的近乎 no-op。 注意本身不持鎖,呼叫端(start/action/… 與指令流程)多在 c.getNPCLock() 鎖內叫用。

      Parameters:
      c - 欲結束對話的客戶端連線
    • getCM

      public final NPCConversationManager getCM(MapleClient c)
      取得指定連線目前進行中的對話會話管理器。
      Parameters:
      c - 客戶端連線
      Returns:
      該連線進行中的 NPCConversationManager;若無進行中對話則為 null