AI,或稱為 "人工智慧",可負責自動開始進行 任務(Task)。
任務是一組要被執行的行動。"任務" 較寬鬆的定義是:任何無法立即被執行的一組行動都叫做任務。舉例來說,攻擊一隻魔物是一項任務、按路線行走(走至一特定目的地)是一項任務。但是定義要怎麼攻擊一隻魔物、定義要打哪一隻魔物,就不是任務了:它是由 AI 負責的。簡短地說:任務負責 做 事,AI 負責 決定要做什麼。
AI 監控著 環境 的變化。環境指的也許是目前在視野範圍內的某角色、物品欄內的物品、人物素質屬性...等等。基本上任何事物都是 "環境" 的一部分。有關環境的資訊(儲存在 OpenKore 的全域變數中)由 網路子系統 來記錄。
AI 會基於目前環境的狀況來做決定與對任務做適當的初始化。舉例來說,假如 AI 注意到有一隻魔物出現了,它能把 "攻擊" 任務做初始化。當然,必要時 AI 也可以中止正在執行中的任務。底下的圖解說明了這個概念:

圖 1: AI 子系統概要圖
Contents |
任務架構是從 AI 獨立出來的。AI 依賴於任務架構,但反之則否。任務架構的中間部分為 Task 類別與 TaskManager 類別。

圖 2:任務架構概要圖
任務的架構遵從 Unix 的哲學:do one thing, but do it well。任何任務類別應該要專門用在唯一一件事上,但儘可能地做好它。每一個任務在它自己的任務類別中被履行。一個任務可以被開始、取消、中斷與繼續。一個任務類別包含可著手處理上述所有動作的程式碼。
如以下類別圖解所示,對於每件任務都有專門化的任務子類別:

圖 3:任務類別階系圖
有些任務會與其它任務衝突到。舉例來說,假設我們有一個 "use skill"(使用技能)的任務與一個 "walk"(走路)的任務。它們無法同時地被執行,當你在走路時你無法使用技能,而當你在使用技能時你無法走路,也就是說,這兩個任務互相 衝突 了。
要解決這個問題,我們引進了 mutex 的觀念(mutual exclusion object(譯者註:不太知道怎麼翻,暫時翻做「互斥物件」吧!)的簡寫)。一個任務有一組 mutexes,那些 mutexes 定義了這個任務所使用的 共享資源 是什麼種類。當兩個任務共享一個共同的 mutex 時,那麼它代表那些任務無法同時被執行。在先前的 "use skill" 與 "walk" 任務的例子裡,被共享的資源是角色的行走能力。讓我們針對 "walk" 資源來呼叫這個 mutex。那麼兩個任務在它們的 mutex 群裡都有這個 mutex。
這裡還有另外一個問題。假設我們有以下假定的情況。
這時 OpenKore 應該要怎麼做?它應該保持 "attack monster" 任務繼續執行,或是中斷 "attack monster" 任務並執行 "run away" 任務呢?對我們手動的人來說,答案很明顯地應該是後者。但對於電腦程式來說,答案並不太明確。
為了要解決這個問題,我們引進了 優先度(priority) 的觀念。假如任務 A 比 B 有較高的優先度,且兩個任務互相有了衝突,那麼 B 會被中斷然後 A 會先被執行。
藉由 mutexes 與 優先度,我們現在有方法可以定義任務間的衝突了。現在我們必須解決它們,為了這麼做我們需要一個任務管理器 (task manager)。
AI 首先創造一個任務,然後把它傳給 TaskManager。TaskManager 將會決定什麼時候該執行一個任務,什麼時候該中斷或繼續一個任務,這會由任務間提供的衝突的定義來決定。它將會確定互相有衝突的任務不會同時被執行,而不互相衝突的任務則可。
為了達到目的,任務可以執行它自己的子任務,如圖 2 所示。舉例來說,假設我們有一個 "walk" 的任務與一個 "attack monster" 的類別。"attack monster" 的類別可以在它內部使用 "walk" 任務以便走向魔物。但就外部(任務管理器與 AI)來說,只有一個任務正在執行:"attack" 任務。外部不知道,也不必理會一個任務是否有子任務。因此,任務要履行的細節是被隱藏起來的,即 概括。
要重新使用其它的任務,更多複雜的任務可以更容易地被概括。一個任務對它的子任務有完全的控制權,即子任務不會被任務管理器所管理。用以下的依賴圖表做為舉例說明。
注意這個依賴只有單方向性的。Task::Move 知道有 Task::SitStand,但反之則否。並且注意這個結構是階層的:Task::MapRoute 只知道有 Task::Route 與 Task::CalcMapRoute。它並不知道,也不需要知道 Task::Route 在內部使用了 Task::Move。
我們也應該注意子任務只會有唯一一個 "父" 任務。任務是一個 類別,一個任務可以無限制地創建許多事件。所以假如有兩個任務都需要 sit/stand 功能的話,那麼它們會各自創建出它們自己的 Task::SitStand 事件。
任務類別擁有以下的 queries(請求?):
以下的指令是可利用的:
請參見 the Task class API documentation for the full specification.
最重要的方法是 iterate()。多數的方法在任務類別中都已經幫你執行好了,但是你必須自己執行 iterate(),以安排設計你的任務的行為。這個方法會接連不斷地被任務管理器(或是被父任務)呼叫,直到任務完成為止。iterate() 儘可能地使用較小的時間進行步驟。假如你不如此做(例如,假如你呼叫了 sleep 10)那麼整個應用程式將會被凍結住。當你的任務完成了,可以呼叫 setDone() 或 setError() 以標記這個任務為已完成。通常,iterate() 應該不會用超過 10 毫秒(miliseconds)的時間。
呼叫 iterate() 的程序由以下的圖表說明:
舊式的 Kore AI 設計為我們服務很長的一段時間了,但它正表現出它的極限度。舉例來說,flee-from-target 很難執行,因為它會對事物中相當多的地方產生衝突。Moving-to-a-monster-before-attacking 有許多難以維修的 bugs,是關於與 attack-routing AI 的相互影響。讓我們分析一下這些問題。
它是最基礎的事物之一。你必須要移動到魔物旁邊才能攻擊它。但就算是這麼簡單的事也不見得做得好。attack AI(攻擊的 AI)觸發了 route AI(計算路徑的 AI),而 attack AI會先暫時中止直到 route AI 做完。當你在移動至魔物的舊位置時魔物也正移動至新位置,你必須等待直到 route AI 完成才能做下一步驟,所以你無法立即對魔物的移動做出反應。這大概就是造成 "在魔物附近晃動" 問題的原因。
好,那並不完全是事實。目前假如 OpenKore 偵測到魔物有移動的話,則會取消 route AI。但手法不很漂亮:
# 魔物已經移動;停止移動並讓 attack AI 重新調整路徑 AI::dequeue; AI::dequeue if (AI::action eq "route");
注意有兩個 AI::dequeue() 呼叫。這是因為 route 在內部觸發了 move。我們干涉了其它 AI 的內部運作。這樣不好,尤其假如它們的內部運作有一天改變了的話。
好,以上的例子其實也沒那麼壞,但它很快地將會越來越糟:
} elsif (((AI::action eq "route" && AI::action(1) eq "attack") || (AI::action eq "move" && AI::action(2) eq "attack"))
這是用來偵測 route AI 是否由 attack AI 所觸發的程式碼。
當我們鍵入 'ss' 指令以使用技能在我們身上時,Kore 會觸發一個 "skill_use" 的 AI 序列,這個序列將會進行一個必要的行動以使用技能。然而,當 AI 無效時這就沒辦法做了。這個問題最近被修正了,但卻是用一個很難看的方法:"skill_use" AI 區塊被修改成忽略 'AI enabled'(AI 有效化)的設定選項。
在舊式的 AI 設計中,我們看見了每一個 AI 區塊特別地檢查了哪一個 AI 序列目前正在活動中。基於此檢查而使它決定自己是否要開始活動。這種檢查的例子像是:
##### AUTO-SKILL USE #####
if (AI::isIdle || AI::is(qw(route mapRoute follow sitAuto take items_gather items_take attack))
|| (AI::action eq "skill_use" && AI::args->{tag} eq "attackSkill")) {
這避免了自動技能使用 AI 區塊自我激活,當例如 "" AI 序列是活動中的時候。
這種方法的問題在於:
但是舊式的設計實際上的需求就是要避免衝突。Mutexes 與 優先度 允許你去定義衝突而不用明確的知道關於所有其它任務的知識。任務管理器可以只在有需要時重新安排任務。
有些任務不只檢查哪一個 AI 序列正在進行,而且會檢查 AI 序列是哪一種類的。舉例來說,當我們目前正在走路時,auto-attack(自動攻擊)AI將會使一個 "attack" AI 序列有效化,但當這個走路是由使用者下指令而觸發的話就不會有效化。程式碼不在此列出,它寫得極端地醜。決定一個路徑 AI 是否會由攻擊的 AI 來觸發的程式碼(如附錄 A 所示)也是寫得極端地醜。舊式的 Kore AI 是完全非物件導向式的。
假如我們使用物件導向設計與概括的話這些種種細節都將獲得解決。攻擊的任務不再需要檢查目前的有效任務是否為 "(由攻擊任務所觸發的路徑任務) 或 (由路徑任務所觸發的移動任務及由攻擊任務所觸發路徑任務)"。
為什麼我要對 AI 與 任務 做一個區分的原因是為了解決 "當 AI 無效時使用技能" 的問題,這個問題描述於附錄 A。假如我們把 "使用技能(skill_use)" 的 AI 變成一個任務的話,那麼我們可以單獨地初始化該任務,無論 AI 是有效中的或無效中的。假如 AI 正在無效中那麼 AI 不會自動初始化技能任務,但使用者仍可以手動執行它。
任務簡單說就是一個類別,這個類別為了要進行複雜的行動而寫了一些程式碼在裡面。關於"複雜的行動"我指的是無法只傳送單一一個 message 給伺服器就可以被執行完成的行動 ── 舉例來說,那些你必須重新嘗試幾次的事情,或那些需要一點時間來完成的事情。技能的使用是個好例子:有時伺服端不會立刻讓你使用這個技能(因為 lag,或因為你正在攻擊中,或因為你正在移動中,或任何其它)。所以這項任務會企圖重新嘗試個幾次,直到該技能被用出來為止。
不會,一個子任務只會有一個 "父" 任務。一個任務是一個 類別,而我們可以創造不限個數的許多事件在其中。所以假如兩個任務都需要 sit/stand 功能的話,那麼它們各自會創造它們自己的 Task::SitStand 事件。
一個任務 是 一個類別,但反之則否。就像鴨子是鳥類,但鳥類不一定是鴨子。
既然任務架構十分倚賴物件導向的概念,那麼對此的熟練度就很重要。這裡有一些引導教學可以看(英文):