ホムンクルスAIマニュアル

目次

  1. 紹介
  2. スクリプト作動構造
  3. ラグナロククライアント内蔵関数説明
  4. ラグナロククライアント内蔵定数説明
    1. GetV 関数に使われる定数
    2. GetV (V_MOTION, id) に対する返り値
    3. GetV (V_HOMUNTYPE, id) に対する返り値
    4. GetMsg (id), GetResMsg (id) によって返されるテーブル構造
    5. ホムンクルスのスキルID
  5. 基本的に提供されたスクリプト説明
    1. 人工知能スクリプトの必須要素
    2. 有限状態遷移機械
    3. Util.lua
    4. AI.lua
  6. その他

1. 紹介

ラグナロクオンライン(以下RO)ゲーム内ホムンクルスの行動は、
ROクライアントプログラムが設置されたフォルダの AI フォルダ中にある
AI.lua, Util.lua によって制御されます。

ROゲーマーは自分の目的に合った人工知能を自ら作ったり、
他人が作った人工知能を使うことができるようにすることが目的です。

スクリプトはROクライアントと連動する一種のプログラムです。
文法的な間違いがあったら動きませんし、論理的な間違いがあると思い通りに
作動しません。

RO開発室ではスクリプトを直接編集しなくてもゲームを楽しめるよう、
良いスクリプトを提供する予定です。

プログラミングに慣れない使用者に対しても自分の目的に合う人工知能を
実現するのに役に立つように修正、補っていく予定です。

プログラミングに興味がある使用者のためにも持続的に機能を追加する予定です。

スクリプトを作成する言語はルア(Lua) です。 (http://www.lua.org/)

2. スクリプト動作構造

ROクライアントはホムンクルスが新たに生成される時、
AI.lua, Util.lua ファイルを解釈してホムンクルスのAIを機能させます。

ホムンクルスが新たに生成される時点は次の通りです。

  1. ホムンクルスが誕生する時
  2. 死んだホムンクルスを生き返らせた時
  3. キャラクター選択画面でホムンクルスを所有したキャラクターを選択してゲームを始める時
  4. ホムンクルスを所有したキャラクターがハエの羽、蝶の羽を使う時
  5. ホムンクルスを所有したキャラクターがワープポータルを利用する時
  6. ホムンクルスを所有したキャラクターがカプラ移動サービスを使う時

上の事項の共通点はホムンクルスがゲーム空間に新たに現われる時です。

AI.lua ファイルが解釈された後に、ROクライアントプログラムは
AI.lua スクリプトで AI (id) 関数を実行させます。
id はゲーム内でホムの固有番号です。id の値は、ROクライアントがAIスクリプトに伝達します。
AI(id) 関数の中身を編集して AI を変更できます。
基礎的なホムの行動、すなわち移動、攻撃、スキル使用等の関数は
ROクライアントプログラムに含まれています。自分が構想した適切な状況で、
提供される関数を実行させれば良いです。

構成

各種AIファイルは以下のようになっています。

AI/

基本AIファイルを置くフォルダです。
基本AIファイルのアップデートで変更されます。

AI/USER_AI/

ユーザーAIファイルを置くフォルダです。
基本AIファイルのアップデートで変更されません。

Const.lua は、ROクライアントプログラム内の各種定数に対する情報を持っています。
これは AI.luaUtil.lua から参照されます。
Const.luaUtil.lua は、 AI.lua から参照されるようになっています。
現在 Util.lua にはリスト、いくつかの単純な機能の計算関数があります。

ホムンクルスAIの必須条件は AI.lua ファイルと AI.lua ファイル内に定義された
AI (id) 関数です。この二つの条件が最低条件となります。
すなわち Const.luaUtil.lua から選択されます。
しかしスクリプトを作成する時さまざまな情報が必要でしょう?

/hoai

基本AIとユーザーAIを切り替えます。

既存AIフォルダの下に USER_AI フォルダが追加され、[ /hoai ]
コマンドで順にフォルダ経路が変わるようになります。
ユーザーAIファイルは USER_AI フォルダに入れて下さい。
(ユーザーAIファイルは基本AIファイルのアップデートに関係なくそのままになります。)

先行入力

移動と攻撃コマンドを先行入力できます。

(詳細処理過程はAI/ホムンクルス人工知能スクリプト説明書.htm(本ページ)を参考にして下さい)

3. ラグナロククライアント内蔵関数説明

# id : ゲーム内の物体が持つ固有番号

1) MoveToOwner (id)

id : ホムンクルスの id
返り値: なし
機能 : ホムンクルスを主人の元に移動させる。

2) Move (id,x,y)

id : ホムンクルスの id
x : 目的地横座標
y : 目的地縦座標
返り値 : なし
機能 : ホムンクルスを目的地に移動させる。

3) Attack (id1,id2)

id1 : 攻撃者
id2 : 被攻撃者
返り値 : なし
機能 : ホムンクルスに id2 を攻撃させる。

4) GetV (V_,id)

V_... : 物体の属性を表す定数
id : 属性の対象
返り値 : V_... によって変わる。例えば、 V_POSITION の場合は現在の x, y 座標、 V_HP の場合は HP である。
機能 : id の属性(V_...) を得る。 属性を表す定数は Util.lua に定義されている。
属性に対する詳細内容は‘4- ラグナロククライアント内蔵定数説明’を参照する。

5) GetActors ()

返り値 : id を返します。Luaのテーブル形式(配列[要素番号])で返される。
機能 : キャラクターの視野範囲にあるキャラクター、NPC、モンスター、アイテム、スキルの id を取得する。
最後手前の要素はホムンクルスid, 最後の要素は主人のidが代入される。

6) GetTick ()

返り値 : 数字(ミリ秒, ms)
機能 : コンピューターの時間を取得します。
この値は、コンピューターが始める時 0 で始めて、 1/1000 秒ごとに 1
ずつ増加し、現在の値。

7) GetMsg (id)

id : ホムンクルスの id
返り値 : ラグナロククライアントから伝達したメッセージ。Luaのテーブル形式(配列[要素番号])で返される。
機能 : 使用者の直接的なコマンド等をスクリプトに伝達する。

8) GetResMsg (id)

id : ホムンクルスの id
返り値 : ラグナロククライアントから伝達した予約メッセージ。Luaのテーブル形式(配列[要素番号])で返される。
機能 : 使用者の直接的な予約コマンド等をスクリプトで伝達する。

10) SkillObject (id,level,skill,target)

id : ホムンクルスの id
返り値 : なし
機能 : id に、スキルLv.(level) を使う。

11) SkillGround (id,level,skill,x,y)

id のある XY 座標に対してスキルLv.(level) を使う。

13) IsMonster (id)

id : ゲーム内の物体
返り値 : id がモンスターなら 1を返し、そうでないなら 0を返す。
機能 : モンスターを判別する。

14) TraceAI (string)

string : TraceAI.txt ファイルに記録される内容。文字列ではなければならない。
機能 : 実行中のスクリプトの現在状態を記録して分析に利用する。

4. ラグナロククライアント内蔵定数説明

内蔵定数は Const.lua に定義されています。

4-1 GetV 関数に使われる定数


V_OWNER            =  0 -- 主人 ID
V_POSITION         =  1 -- 座標 (x, y)
V_TYPE             =  2 -- 種類(未実装)
V_MOTION           =  3 -- 現在動作
V_ATTACKRANGE      =  4 -- 物理攻撃範囲(未実装。現在は1セルに固定)
V_TARGET           =  5 -- 攻撃、スキル使用対象 ID 
V_SKILLATTACKRANGE =  6 -- スキル攻撃範囲(未実装)
V_HOMUNTYPE        =  7 -- ホムンクルス 種類
V_HP               =  8 -- HP (ホムンクルスと主人にだけ適用)
V_SP               =  9 -- SP (ホムンクルスと主人にだけ適用)
V_MAXHP            = 10 -- 最大 HP (ホムンクルスと主人にだけ適用)
V_MAXSP            = 11 -- 最大 SP (ホムンクルスと主人にだけ適用)
V_MERTYPE          = 12 -- 傭兵 種類

4-2 GetV (V_MOTION, id) に対する返り値


MOTION_STAND    =  0 -- 立っている
MOTION_MOVE     =  1 -- 移動中
MOTION_ATTACK   =  2 -- 攻撃中
MOTION_DEAD     =  3 -- 死んで倒れる
MOTION_DAMAGE   =  4 -- ダメージを受けた時
MOTION_BENDDOWN =  5 -- かがむ(アイテムを拾う、罠を置く)
MOTION_SIT      =  6 -- 座っている
MOTION_SKILL    =  7 -- スキル攻撃中(インベ、カートレボリューション、ラウドボイス)
MOTION_CASTING  =  8 -- 詠唱
MOTION_ATTACK2  =  9 -- 攻撃
MOTION_SPIRAL   = 11 -- スパイラルピアース
MOTION_TOSS     = 12 -- 投げる(スピアブーメラン、ポーションピッチャー、バイオプラント)
MOTION_COUNTER  = 13 -- オートカウンター
MOTION_PERFORM  = 17 -- 演奏
MOTION_UPPER    = 19 -- ノピティギ上昇中
MOTION_DOWNER   = 20 -- ノピティギ下降中
MOTION_SOUL     = 23 -- ソウルリンカー、魂使用
MOTION_IN       = 25 -- ノピティギ着地、落法
MOTION_BIGTOSS  = 28 -- 大きく投げる(スリムポーションピッチャー、アシッドデモンストレーション)
MOTION_DESPERADO   = 38 -- デスペラード
MOTION_XXXXXX      = 39 -- 不明(コインフィリップ/ダスト?)
MOTION_FULLBASTERD = 42 -- フルバスター

-- 以前との互換用
MOTION_PP       = MOTION_TOSS
MOTION_SLIMPP   = MOTION_BIGTOSS
MOTION_PICKUP   = MOTION_BENDDOWN

4-3 GetV(V_HOMUNTYPE, id) に対する返り値


LIF           =  1 -- リーフ
AMISTR        =  2 -- アミストル
FILIR         =  3 -- フィーリル
VANILMIRTH    =  4 -- バニルミルト
LIF2          =  5 -- リーフ2
AMISTR2       =  6 -- アミストル2
FILIR2        =  7 -- フィーリル2
VANILMIRTH2   =  8 -- バニルミルト2
LIF_H         =  9 -- 進化したリーフ
AMISTR_H      = 10 -- 進化したアミストル
FILIR_H       = 11 -- 進化したフィーリル
VANILMIRTH_H  = 12 -- 進化したバニルミルト
LIF_H2        = 13 -- 進化したリーフ2
AMISTR_H2     = 14 -- 進化したアミストル2
FILIR_H2      = 15 -- 進化したフィーリル2
VANILMIRTH_H2 = 16 -- 進化したバニルミルト2

4-4 GetMsg(id), GetResMsg(id) によって返されるtable構造


NONE_CMD = 0 -- コマンドなし
{コマンド番号}

MOVE_CMD = 1 -- 移動
{コマンド番号, X座標, Y座標}

STOP_CMD = 2 -- 停止
{コマンド番号}

ATTACK_OBJET_CMD = 3 -- 攻撃
{コマンド番号, 目標ID}

ATTACK_AREA_CMD = 4 -- 地面指定攻撃
{コマンド番号, X座標, Y座標}

PATROL_CMD = 5 -- パトロール(往復移動)
{コマンド番号, X座標, Y座標}

HOLD_CMD = 6 -- ホールド
{コマンド番号}

SKILL_OBJECT_CMD = 7 -- 対象指定スキル使用
{コマンド番号, 選択レベル, 種類, 目標ID}

SKILL_AREA_CMD = 8 -- 範囲スキル使用
{コマンド番号, 選択レベル, 種類, X座標, Y座標}

FOLLOW_CMD = 9 -- 主人に追従する
{コマンド番号}

4-5 ホムンクルスのスキルID


-- リーフ
SKILL_TOUCH_OF_HEAL       = 8001 -- 治癒の手
SKILL_EMERGENCY_AVOID     = 8002 -- 緊急回避
SKILL_BRAIN_SURGERY       = 8003 -- 脳手術
SKILL_MENTAL_CHANGE       = 8004 -- メンタルチェンジ
-- アミストル
SKILL_CASTLING            = 8005 -- キャスリング
SKILL_DEFENCE             = 8006 -- ディフェンス
SKILL_ADAMANTIUM_SKIN     = 8007 -- アダマンティウムスキン
SKILL_BLOOD_LUST          = 8008 -- ブラッドラスト
-- フィーリル
SKILL_MOONLIGHT           = 8009 -- ムーンライト
SKILL_FLEET_MOVE          = 8010 -- フリットムーブ
SKILL_OVERED_SPEED        = 8011 -- オーバードスピード
SKILL_SBR44               = 8012 -- S.B.R.44
-- バニルミルト
SKILL_CAPRICE             = 8013 -- カプリス
SKILL_CHAOTIC_VENEDICTION = 8014 -- カオティックベネディクション
SKILL_CHANGE_INSTRUCTION  = 8015 -- チェンジインストラクション
SKILL_BIO_EXPLOSION       = 8016 -- バイオエクスプロージョン

5. 基本的に提供されたスクリプト説明

5-1 人工知能スクリプトの必須要素

スクリプトファイルは一般的なテキストファイルです。
メモ帳のようなソフトやエディタでスクリプトファイルを作成します。
ただ Lua という言語で書かれているのでファイルの拡張子に lua を使います。


function AI (myid)

end

最初にこれを書いてから、AIフォルダにある既存 AI.lua を他の所に移したり
名前を変更した後、新たに作成したAIファイルをコピーします。
そしてROクライアントを再起動すれば、まともに作動します。
ただ、ホムは何もせず、じっとしています。

5-2 有限状態遷移機械

人工知能を作成する方法は様々あります。その中で単純で、たくさん使われる方法が 有限状態機械(FSM, Finite State Machine)です。
有限状態機械 - google

有限状態遷移機械の学術的定義は関連書籍に詳しく出ていますが、
普通の方が常識的に理解していることです。

主題として、私のホムンクルスを例にあげます。
まずホムンクルスにどんな行動をとってほしいですか?

では、例を書いてみます。

状況によって幾多の要求事項があります。
要求事項が整理できたら、これからホムンクルスの立場になって考えて見ましょう。

待機 (IDLE)

周りに何らのモンスターがいなくて主人もじっとする状況を思い浮かべます。
そのまま待機する状態です。

追跡 (CHASE)

主人が攻撃され、ホムも攻撃されたら攻撃した物体をホムが敵に認識して追い掛けなければなりません。
何かを追い掛ける状態です。

攻撃 (ATTACK)

少なくとも攻撃範囲の中にあれば物理攻撃やスキルを使います。
何かと戦う状態です。

追従 (FOLLOW)

少なくとも消滅または、追い掛けることができない位遠く逃げてしまえば
もう主人の元に戻らなければなりません。
主人に追従する状態です。

上で言った四大状態をそれぞれ待機 (IDLE), 追跡 (CHASE), 攻撃 (ATTACK), 追従 (FOLLOW) 状態と
定義します。

そして各状態でどんな事態が起きたら別の状態に変化するかどうかを決めます。
そして各状態でしなければならないことを決めておきます。
いくつかの条件設定をもっと追加した後、これを図に描くと以下の通りになります。

有限状態遷移図

FSM

くま○〜さんによる詳しい状態遷移図を引用します。

標準AIの状態遷移図 from くま○〜のAIファイル配布

最初の状態は待機状態です。周り状況の変化によってホムの状態は変化します。
そしてその都度、適切な行動をするように設定すればいいです。
もう目的は決まったのでいくつかの準備から実際にスクリプトを作成します。

5-3 Util.lua

どんな作業をする時でも、私は便利な作業のために道具を準備します。
ホムンクルスのAIを作ろうとすれば必須要素である AI.lua と AI(id) 関数だけ
あればよいのですが、あらかじめいくつかの道具を作っておいた方が良いです。

この道具は主にテーブルや関数の形式です。
これらを Util.lua に作成し、 AI.lua で参照するようにします。

空のファイルの一番上に Const.lua を参照するよう


require "./AI/Const.lua"

を書いてください。

色々なデータを順に保存して、順に取り出すためのリストが必要な場合があります。
ホムンクルスの人工知能の場合は予約コマンドを順に保存して取り出して来る必要があります。
リストの名前を List と言っていくつかの関数を追加します。

Util.lua 参照)


List.new ()                  -- 新しいリスト(返り値)
List.pushleft (list, value)  -- リストの左側に要素追加
List.pushright (list, value) -- リストの右側に要素追加
List.popleft (list)          -- リストの左側最初の値を取り出す
List.popright (list)         -- リストの右側最初の値を取り出す
List.clear (list)            -- リストをクリアする
List.size (list)             -- リストに入っている要素の個数

よく使われるいくつかの計算関数も追加します。


GetDistance (x1,y1,x2,y2) -- 二つの座標間のセル距離(定数)
GetDistance2 (id1, id2)   -- 二つの物体間のセル距離(定数)
GetOwnerPosition (id)     -- 主人の位置
GetDistanceFromOwner (id) -- 主人との距離
IsOutOfSight (id1,id2)    -- id1と id2 がお互いに見える距離なら
true, 見えない距離なら false を返します。
IsInAttackSight (id1,id2) -- id1の攻撃やスキル使用範囲に
id2が入っていれば true, 入っていなければ false を返します。

5-4 AI.lua

では、 AI.lua を作成してみましょう。空白のみのテキストファイルを一つ作り、 AI.lua に名前を変えます。メモ帳で AI.lua ファイルを開きます。

ファイル内の一番上に Const.lua , Util.lua を参照するように


require "./AI/Const.lua"
require "./AI/Util.lua"

を書いてください。
以下の空白に要素(関数)


function AI (myid)

end

は少なくとも必須です。

これだけでも、一つのAIが出来上がります。 しかし、空っぽな AI(myid) 関数からなる AI を使うと、 ゲーム内でホムンクルスはそのままじっとしています。 これは最終目的ではありません。

目的のいくつかの状態を定義します。


IDLE_ST        = 0  -- 待機状態
FOLLOW_ST    = 1  -- 主人追従状態
CHASE_ST    = 2  -- ターゲット追跡状態
ATTACK_ST    = 3  -- 攻撃状態

そして現在ホムンクルスの状態を保存する変数が必要です。
またホムンクルスのid, 敵id, 目的地座標等々も保存する必要があります。
ホムンクルスの状態は、最初は待機状態です。


------------------------------------------
-- グローバル変数(プログラム全体で共通で使うフラグみたいなもの)
------------------------------------------
MyState                = IDLE_ST    -- ホムの現在状態(初期値は待機状態)
MyEnemy                = 0        -- ホムがターゲットしている敵のキャラクターID
MyDestX                = 0        -- ホムの目標座標X(移動先 / スキル使用座標)
MyDestY                = 0        -- ホムの目標座標Y(移動先 / スキル使用座標)
MyPatrolX            = 0        -- ホムがパトロール(往復移動)する始点座標X
MyPatrolY            = 0        -- ホムがパトロール(往復移動)する始点座標Y
-- (主人が先行で複数コマンド入力した場合、ここに順番に記憶されます)
ResCmdList            = List.new()    -- 先行入力コマンドリスト
MyID                = 0        -- ホム自身のキャラクターID
MySkill                = 0        -- ホムが使おうとしているスキル
MySkillLevel            = 0        -- ホムが使おうとしているスキルレベル
------------------------------------------

function AI (myid)
    MyID = myid    -- ホム自身のキャラクターid
end

待機状態を処理する関数 OnIDLE_ST() を定義します。
そして AI(myid) 関数内に作動するように編集します。


function    OnIDLE_ST ()
    
    TraceAI ("OnIDLE_ST")

    local cmd = List.popleft(ResCmdList)
    if (cmd ~= nil) then    -- コマンド入力があれば
        ProcessCommand (cmd)    -- 対応するコマンドの受付処理を行う
        return 
    end

    local    object = GetOwnerEnemy (MyID)    -- 主人を攻撃している敵のキャラクターidを取得
    if (object ~= 0) then            -- 主人を攻撃している敵がいるならば
        MyState = CHASE_ST    -- ホム状態をターゲット追跡状態にする
        MyEnemy = object    -- objectの敵をターゲットにする
        TraceAI ("IDLE_ST -> CHASE_ST : MYOWNER_ATTACKED_IN")
        return    -- 戻る
    end

    object = GetMyEnemy (MyID)    -- ホムの敵のキャラクターidを取得
    if (object ~= 0) then        -- ホムの敵がいるならば
        MyState = CHASE_ST    -- ホム状態をターゲット追跡状態にする
        MyEnemy = object    -- objectの敵をターゲットにする
        TraceAI ("IDLE_ST -> CHASE_ST : ATTACKED_IN")
        return    -- 戻る
    end

    local distance = GetDistanceFromOwner(MyID)    -- 主人との距離を取得
    if ( distance > 3 or distance == -1) then    -- 主人までの距離が距離が3より遠いか視界外ならば
        MyState = FOLLOW_ST    -- ホム状態を追従状態にする
        TraceAI ("IDLE_ST -> FOLLOW_ST")
        return;    -- 戻る
    end

end

function AI (myid)
    MyID = myid    -- ホム自身のキャラクターid
     if (MyState == IDLE_ST) then    -- 待機状態
        OnIDLE_ST ()
    end
end

「主人を攻撃している」、「ホムの敵がいる」場合追跡状態に転移します。
この二つとも当てはまらない場合、主人との距離を計算して一定距離以上離れたら
主人追従状態に切り替えます。その場合「主人を攻撃している」、「ホムの敵がいる」
という条件はどうやって判定しますか?

「主人を攻撃している」と言う条件は周りの物体の中でモンスターであることが
その対象が主人の場合になります。それならモンスターではない一般プレーヤーの
キャラクターが主人を攻撃する場合はどうやって分かりますか?
そのキャラクターの動作を調査して見れば分かります。


function    GetOwnerEnemy (myid)
    local result = 0        -- 敵のキャラクターid
    local owner  = GetV (V_OWNER,myid)    -- 主人自身のキャラクターid
    local actors = GetActors ()    -- 周りのキャラクターidを全部取得
    local enemys = {}    -- 敵のリスト
    local index = 1        -- インデックス番号(初期値1)
    local target        -- ターゲットのキャラクターid

    -- (1,actors[1])、(2,actors[2])、…のペアを繰り返す
    -- 周りのキャラクターidをひとつひとつチェックして、そのキャラクターが
    -- 主人を攻撃しようとしていたら、敵として敵リストに追加する
    for i,v in ipairs(actors) do
        if (v ~= owner and v ~= myid) then    -- 主人かホム自身じゃなければ
            target = GetV (V_TARGET,v)    -- そのキャラクターのタゲを取得
            if (target == owner) then    -- タゲが主人だったら
                if (IsMonster(v) == 1) then    -- モンスターだったら
                    enemys[index] = v    -- 敵のリストに追加
                    index = index+1        -- インデックスを1増やす
                else    -- モンスターじゃなければ
                    local motion = GetV(V_MOTION,i)    -- そのキャラクターのモーションを取得
                    -- モーションが攻撃モーションだったら
                    if (motion == MOTION_ATTACK or motion == MOTION_ATTACK2) then
                        enemys[index] = v    -- 敵のリストに追加
                        index = index+1    -- インデックスを1増やす
                    end
                end
            end
        end
    end

    local min_dis = 100    -- 最小距離(初期値は100)
    local dis        -- 距離

    -- (1,enemys[1])、(2,enemys[2])、…のペアを繰り返す
    -- 敵リストの中で、ホムから一番近い敵をホムの攻撃対象とする
    for i,v in ipairs(enemys) do
        dis = GetDistance2 (myid,v)    -- ホムと敵との距離を取得
        if (dis < min_dis) then        -- 最小距離より近ければ
            result = v    -- その敵をターゲットとする
            min_dis = dis    -- その敵との距離を最小距離とする
        end
    end
    
    return result    -- ターゲットを返す
end

結果、値が 0 の場合主人は攻撃を受けていることが分かります。

「ホムの敵がいる」条件はどうやって判断しますか?
もしホムンクルスがモンスターを先に攻撃するようにしたければ、
周りにモンスターがいたら少なくあると判断すれば良いです。
もしホムンクルスが攻撃を受ける場合だけホムンクルスが応じるようにしたければ、
ホムンクルスを攻撃する対象があるのかを検査すれば良いです。
この二つの中一つの選択は先攻、非先攻ホムンクルスを決めます。


function    GetMyEnemy (myid)
    local result = 0    -- 敵のキャラクターid

    local type = GetV (V_HOMUNTYPE,myid)    -- ホムのタイプを取得する
    -- ホムが LIF か AMISTR ならば
    if (type == LIF or type == LIF_H or type == AMISTR or type == AMISTR_H) then
        result = GetMyEnemyA (myid)    -- 敵取得タイプA(非先攻型)
    -- ホムが FILIR か VANILMIRTH ならば
    elseif (type == FILIR or type == FILIR_H or type == VANILMIRTH or type == VANILMIRTH_H) then
        result = GetMyEnemyB (myid)    -- 敵取得タイプB(先攻型)
    end
    return result    -- ターゲットを返す
end


-------------------------------------------
-- 敵取得タイプA(非先攻型)
-------------------------------------------
function    GetMyEnemyA (myid)
    local result = 0    -- 敵のキャラクターid
    local owner  = GetV (V_OWNER,myid)    -- 主人自身のキャラクターid
    local actors = GetActors ()    -- 周りのキャラクターidを全部取得
    local enemys = {}    -- 敵のリスト
    local index = 1        -- インデックス番号(初期値1)
    local target        -- ターゲットのキャラクターid

    -- (1,actors[1])、(2,actors[2])、…のペアを繰り返す
    -- 周りのキャラクターidをひとつひとつチェックして、そのキャラクターが
    -- ホムを攻撃しようとしていたら、敵として敵リストに追加する
    for i,v in ipairs(actors) do
        if (v ~= owner and v ~= myid) then    -- 主人かホム自身じゃなければ
            target = GetV (V_TARGET,v)    -- そのキャラクターのタゲを取得
            if (target == myid) then    -- タゲがホム自身ならば
                enemys[index] = v    -- 敵のリストに追加
                index = index+1        -- インデックスを1増やす
            end
        end
    end

    local min_dis = 100    -- 最小距離(初期値は100)
    local dis        -- 距離

    -- (1,enemys[1])、(2,enemys[2])、…のペアを繰り返す
    -- 敵リストの中で、ホムから一番近い敵をホムの攻撃対象とする
    for i,v in ipairs(enemys) do
        dis = GetDistance2 (myid,v)    -- ホムと敵との距離を取得
        if (dis < min_dis) then        -- 最小距離より近ければ
            result = v    -- その敵をターゲットとする
            min_dis = dis    -- その敵との距離を最小距離とする
        end
    end

    return result
end


-------------------------------------------
-- 敵取得タイプB(先攻型)
-------------------------------------------
function    GetMyEnemyB (myid)
    local result = 0    -- 敵のキャラクターid
    local owner  = GetV (V_OWNER,myid)    -- 主人自身のキャラクターid
    local actors = GetActors ()    -- 周りのキャラクターidを全部取得
    local enemys = {}        -- 敵のリスト
    local index = 1            -- インデックス番号(初期値1)
    local type        -- (未使用)

    -- (1,actors[1])、(2,actors[2])、…のペアを繰り返す
    -- 周りのキャラクターidをひとつひとつチェックして、そのキャラクターが
    -- モンスターだったら、敵として敵リストに追加する
    for i,v in ipairs(actors) do
        if (v ~= owner and v ~= myid) then    -- 主人かホム自身じゃなければ
            if (1 == IsMonster(v))    then    -- モンスターだったら
                enemys[index] = v    -- 敵のリストに追加
                index = index+1        -- インデックスを1増やす
            end
        end
    end

    local min_dis = 100    -- 最小距離(初期値は100)
    local dis        -- 距離

    -- (1,enemys[1])、(2,enemys[2])、…のペアを繰り返す
    -- 敵リストの中で、ホムから一番近い敵をホムの攻撃対象とする
    for i,v in ipairs(enemys) do
        dis = GetDistance2 (myid,v)    -- ホムと敵との距離を取得
        if (dis < min_dis) then        -- 最小距離より近ければ
            result = v    -- その敵をターゲットとする
            min_dis = dis    -- その敵との距離を最小距離とする
        end
    end

    return result
end

上のように待機状態を処理するプログラムを作成しました。
待機状態から変化されるアルケミスト追従状態、ターゲット追跡状態も似たやり方で作成し、残りの状態も同じです。

最後にホムンクルスが使用者の直接的なコマンドを遂行するようにすることがあります。
マウスで特定位置に移動させるとか特定モンスターを攻撃させるとか、
キーボードの特定ジャンキーを押してその場所でばかりじっとしているようにするとかの処理です。

使用者が出すコマンドはメッセージ形態でスクリプトに伝達します。
そのメッセージを解釈して、特定コマンドを遂行する状態に変化させれば良いです。
そうしたいならメッセージを受けて解釈する部分と特定コマンドを処理する状態を追加して
状態処理関数を追加すれば良いです。AI(myid) 導入部に次を追加します。


    MyID = myid    -- ホム自身のキャラクターid
    local msg    = GetMsg (myid)        -- 主人が入力したコマンド
    local rmsg    = GetResMsg (myid)    -- 先行入力されたコマンド

    -- コマンドごとの振り分け処理
    if msg[1] == NONE_CMD then        -- 新規コマンド入力がなければ
        if rmsg[1] ~= NONE_CMD then    -- 先行入力されたコマンドがあれば
            if List.size(ResCmdList) < 10 then    -- 先行入力コマンドが10個未満ならば
                List.pushright (ResCmdList,rmsg)    -- 先行入力コマンドリストに追加
            end
        end
    else    -- 新規コマンド入力があれば
        List.clear (ResCmdList)    -- 先行入力コマンドリストをクリア
        ProcessCommand (msg)    -- ホムのステータスを更新する
    end

メッセージを処理する ProcessCommand (msg) は
各メッセージ処理関数 OnMOVE_CMD (msg[2],msg[3]) などを追加して、
それぞれのコマンド遂行状態処理関数 (例えば OnMOVE_CMD_ST ()) も作成します。
コマンド遂行状態が必要な理由はコマンド遂行が終わるまで
コマンド遂行完了を検査するためです。


function    OnCHASE_ST ()

    TraceAI ("OnCHASE_ST")

    if (true == IsOutOfSight(MyID,MyEnemy)) then    -- 敵が視界外に出たならば
        MyState = IDLE_ST    -- ホム状態を待機状態にする
        MyEnemy = 0    -- タゲにしている敵がいたらタゲ解除
        MyDestX, MyDestY = 0,0    -- 目標座標があればクリア
        TraceAI ("CHASE_ST -> IDLE_ST : ENEMY_OUTSIGHT_IN")
        return
    end
    if (true == IsInAttackSight(MyID,MyEnemy)) then    -- 敵が攻撃範囲に入ったら
        MyState = ATTACK_ST    -- ホム状態を攻撃状態にする
        TraceAI ("CHASE_ST -> ATTACK_ST : ENEMY_INATTACKSIGHT_IN")
        return
    end

    local x, y = GetV (V_POSITION,MyEnemy)    -- 敵の座標を取得する
    if (MyDestX ~= x or MyDestY ~= y) then            -- 目標座標に敵がいなければ
        MyDestX, MyDestY = GetV (V_POSITION,MyEnemy);    -- 敵の座標を目標座標にする
        Move (MyID,MyDestX,MyDestY)    -- 敵の座標へ移動開始
        TraceAI ("CHASE_ST -> CHASE_ST : DESTCHANGED_IN")
        return
    end

end

上のような特定状態処理関数で、知りたい内容を TraceAI に入れてくれます。
そしてゲームチャットウィンドウに /traceai と打てばラグナロクオンラインがインストールされたフォルダに
ある TraceAI.txt ファイルにその内容が記録されます。
再び /traceai を入力すれば記録を止めます。色々な変数値を記録したい時は
string.format を利用して文字列を操作します。

6. その他

参考

プログラミング言語ルア (Lua)

ハングル Lua サイト

Lua 5.0 リファレンスマニュアル from yunoの雑記帳

Lua 5.1 リファレンスマニュアル from yunoの雑記帳

Lua5.1(alpha) テスト実行CGI (Executer CGI) from 空想具現化プログラミング

Lua/組み込み系言語 Wiki*

Lua言語リンク集


インデックスに戻ります。