ドキュメント ゲームプレイ拡張

RPG コンパニオン

RPG / カスタムマップで、プレイヤーに AI の相棒をつけます。あなたについて歩き、敵との戦いを手伝い、HP が減れば回復し、話し相手にもなります。4 つのモードがあり、クラスを 1 つ継承していくつかの属性を変えるだけで、あなただけのコンパニオンになります。

対戦だけではありません。RPG やカスタムマップでは、自分に AI の相棒をつけられます。あなたについて歩き、敵との戦いを手伝い、HP が減れば回復してくれて、手が空いているときは話しかけてきます —— セリフはローカル LLM につなぐこともできます。

どう使うかはあなた次第です。 この機能は 3 層の API に分かれていて、下の層から上の層まで、どれも直接使えます。

層内容向いている用途
JASS チャネル g.jassマップ作者が使える 1291 個の JASS 関数を、名前で直接呼び出します(ユニット作成、同盟設定、アイテム付与、名前変更、テキスト表示、ヒーロー蘇生……)自分で遊び方を作りたい
便利 APIg.spawn、g.set_alliance、g.player_slots、g.show_text、g.map_data:よく使う処理をまとめてあります自分用の補助スクリプトを書く
コンパニオンフレームワークopenwar3.companion.Companion + openwar3.talk.Talk:継承していくつかの属性を変えるだけで、追従、援護、回復、会話をこなす相棒になります相棒がほしい

ローカル、または LAN で自分で立てたゲーム専用です。ユニット作成や同盟設定のような操作は、自分のマシンが一方的にワールドを変更するものです。シングルプレイ(コンピューターとの対戦)なら問題ありませんが、マルチプレイでは他のプレイヤーと同期がずれます。そのためマルチプレイでは、JASS チャネルは読み取り専用の関数だけを許可し、コンパニオンは自動的に「話すだけ」に切り替わります。

最速で始める:Farsight でワンクリック起動

  1. マップを選ぶ:Farsight の「インスタンス」ページ →「次のゲームの設定」→ マップで、RPG マップを 1 つ選びます(ゲームディレクトリの Maps 以下にある Scenario、Download 内のマップがすべて一覧に出ます。例:(4)WarChasers)。
  2. スキームを選ぶ:インスタンスカードの「AI スキーム」ドロップダウンで コンパニオンのサンプル(buddy) を選び →「選択」。
  3. テストを開始する:ゲームが起動したら、あなた自身がゲームウィンドウでプレイします。コンパニオン —— 「ヒカリ」という名前のパラディン —— があなたのそばに現れます。

コマンドラインからも起動できます。

python tools/play.py --bot brains/examples/buddy.py --inst 20 --rpg --map "<ゲームディレクトリ>\Maps\Scenario\(4)WarChasers.w3m"

--rpg(スキームのマニフェストでは "judge": false)は、対戦ルールで勝敗を判定しないという意味です。RPG ではヒーローが死んでも蘇生でき、「建物が全滅したら負け」というルールもありません。多くの RPG マップは読み込み後に「任意のキーを押して続行」の画面で止まります。SDK は「ゲーム中なのに、ゲーム時計がずっと 0 のまま」であることに気づくと、自分でスペースキーを 1 回押します(g.press_to_continue()。ゲームウィンドウにキー入力のメッセージを送るだけで、フォーカスは奪いません)。

自分のコンパニオンを書く

from openwar3.companion import Companion
from openwar3.talk import Talk

class MyBuddy(Companion):
    mode = "ally"                        # モード。下の表を参照
    unit = "Hpal"                        # 作るユニット:任意の 4 文字コード。マップ独自のものも可
    nickname = "ヒカリ"
    heal = ("holybolt", "AHhb", 0.55)    # (スペルのオーダー名, 習得するスキル, 主人の HP がこの割合を下回ったら回復);None = 回復しない
    follow_distance = 350
    talk = Talk(persona="元気な小さなパラディン。主人を応援するのが大好き")

4 つのモード

modeコンパニオンの正体説明
ally(デフォルト)空いているプレイヤースロットを 1 つ使い、あなたの同盟者になる自分の色と名前を持ちます(スコアボードや同盟パネルに nickname が表示されます)。あなたからは操作できず、自分で戦います。フレームワークが自動で同盟 + 視界共有に設定します
ownあなたの所有ユニットとして作るいつでも手動で指揮できます。あなたが操作していないときは、AI が代わりに動かします
adoptマップにすでにあるユニットを引き継ぐadopt(g) をオーバーライドしてそのユニットを返します(マップがくれたペットやお供など)
voiceユニットは作らず、話すだけ話し相手、リマインダー。ワールドを変更しないので、マルチプレイでも使えます

空きスロットがなければ、ally は自動で own に切り替わります。マルチプレイの場合やユニットを作れない場合は、自動で voice に切り替わります。

ally モードのコンパニオンが追うのは「そのスロットにいま存在する最適なユニット」(ヒーロー優先)で、特定の 1 体に固定されるわけではありません。実測では、コンパニオンを本物のプレイヤーとして扱い、パラディンを削除してマップ独自のヒーローを支給したマップがありました —— コンパニオンはそのヒーローをそのまま引き継ぎ、マップがそのヒーローに設定したスキルも習得します。ヒーローが死んだら、その場での蘇生を優先します。マップ側が蘇生した場合は、そのまま使い続けます。

ティックごとに何をするか

順番にチェックし、条件が成立した行動を取ります。

順番行動条件調整項目
1撤退自分の HP が 25% 未満で、近くに敵がいる:主人の後ろまで下がるretreat_at
2回復主人の HP が設定値を下回り、スキルのクールダウンが明けていて、距離が 900 以内heal(None で無効)
3援護主人の周りに敵がいる:主人を攻撃している敵 > 主人が攻撃している敵 > 最も近い敵assist_radius、または pick_target をオーバーライド
4追従主人から離れすぎたら追いかける。ある程度以上離れたら、戦闘をやめてまっすぐ戻るfollow_distance、leash
5雑談敵がいないとき、1 〜 2.5 分ごとに一言話すセリフ表

「敵」はゲーム内の同盟関係で判断します(20 秒ごとに更新)。RPG マップには同盟勢力がいくつもあることが多く、「自分以外のプレイヤー」をすべて敵とみなすことはできません。

オーバーライドできるフック:find_master(誰が主人か。デフォルトはローカルプレイヤーのレベルが最も高いヒーロー)、adopt、pick_target、on_poke(主人がコンパニオンを右クリックした)、そして Bot の on_start / on_tick / on_event / on_end。回復、援護、撃破、追従、撤退、発言、蘇生の回数はすべて self.stats に記録され、終了時に出力されます。

呼びかけ方

  • チャットコマンド:チャット欄に -follow(ついてきて)、-stay(その場で待機)、-heal(すぐに回復)、-hi(あいさつ)と入力します。コマンド表は commands で変更し、反応は on_command をオーバーライドして変えます。
  • コンパニオンを右クリック:on_poke が呼ばれます。サンプルでの反応は、主人の HP が満タンでなければ回復を 1 回かけ、満タンなら一言話す、というものです。
  • ポートレート会話:あいさつ、主人が倒れたとき、主人のレベルアップ、コンパニオンの復帰。この数種類のセリフは、ゲーム自身のポートレート会話で話します(下部のポートレートがコンパニオンに切り替わり、画面に字幕が出ます)。それ以外は頭上の吹き出しで表示します。
  • ステータスパネル:画面左側のパネルに、コンパニオンの HP バー、いま何をしているか、気分(ご機嫌 / わくわく / 緊張 / 怖い / 悲しい)、撃破数と回復回数を表示します。キャンバス で描いているので、マルチプレイでも安全です。

会話とローカル LLM

Talk はイベントに応じてセリフを選び、頭上に吹き出しを出します。voice モードのときや吹き出しを出せないときは、画面の左下に表示します。各セリフはスキームのログにも書き込まれるので、何を話したかを後から確認できます。

イベントタイミングイベントタイミング
hello登場したときmaster_low主人の HP が少ない
poke主人に右クリックされたmaster_levelup主人がレベルアップした
fight戦闘開始master_died / master_back主人が倒れた / 復活した
kill敵を 1 体倒した(倒した相手の名前を口にする)buddy_low / buddy_died / buddy_backコンパニオン自身の HP が少ない / 倒れた / 戻ってきた
healed主人を回復したidle / item雑談 / アイテムを拾った

セリフには {master}、{me}、{map}、{enemy}、{level}、{item} といったプレースホルダーを使えます。セリフを変えるなら talk.lines を直接編集し、クールダウンは talk.cooldown で設定します。

ローカル LLM につなぐ:Talk(llm=LocalLLM(url, model))。OpenAI 互換の API ならどれでも使えます(LM Studio、Ollama……)。モデルはバックグラウンドスレッドで回答し、返答が届いてから話します。モデルが起動していない、タイムアウトした、エラーになった場合は固定のセリフを話すので、ゲームが止まることはありません。リクエストはあなたが指定したローカルのアドレスにだけ送られ、その内容はゲーム内で起きたこと(主人の名前、倒した敵)です。

カスタムマップのユニット名

RPG マップのユニット、アイテム、ヒーローの多くはマップ独自に作られたもので(4 文字コードは HC07、I00A のような形)、組み込みの名前表では見つかりません。g.map_data は、いまのゲームのマップファイルを直接読みます。

md = g.map_data
md.name_of("HC07")        # 'Optimus Primo' —— マップで変更された名前を優先
md.hero_names("HC07")     # 称号のリスト
md.hero_skills("OC10")    # マップがこのヒーローに設定したスキル
md.tooltip("I00A")        # 説明文

保護や最適化がかかったマップ(人気 RPG の多く)には標準のオブジェクトデータファイルが含まれていないため、名前はマップ内のテキストデータから読み取ります。実測では、手元の RPG / カスタムマップ 38 個すべての解析に成功し、そのうち 37 個でユニット名を取得できました。

スキームにして共有する

コンパニオンは openwar3.Bot のサブクラスにすぎないので、AI スキーム にして他の人と共有できます。マニフェストに 2 項目を追加します。

{"id": "my-buddy", "name": "わたしのコンパニオン", "entry": "my_buddy.py", "fair": false, "judge": false}

"fair": false:JASS チャネル(ユニット作成、同盟設定)を使うため。"judge": false:対戦ルールで勝敗を判定しないため。

実測記録

2026-09-24、テストインスタンス、WarChasers マップ、2 倍速:

  • JASS チャネルの 18 項目のチェックがすべて合格:プレイヤースロット、ユニットとハンドルの相互変換、実数の戻り値、文字列引数、空きスロットへのユニット作成、同盟設定、名前変更、ユニット削除。プレイヤーレーンからの呼び出しや引数の個数違いは、正しく拒否されました。
  • コンパニオン:「任意のキーを押して続行」を自分で押す → 主人のそばに現れてあいさつ → 主人についてヒーロー選択用のサークルに入り、マップからヒーローを支給されて引き継ぐ → 追従(主人から 200 〜 400)→ 敵と戦い、1 体倒して「お見事!」と言う → HP が減って撤退 → 戦死後にマップによって蘇生され、再び追従。

まだできていないこと

  1. プレイヤーが入力した任意のチャット文は読み取れません。決まったチャットコマンドはすでに使えます。コンパニオンと本当に自由に会話するには、入力されたテキストそのものを取得する必要があります。
  2. コンパニオンは個々のマップの遊び方(クエスト、ショップ、ストーリー)を理解していません。できるのは汎用的な追従、援護、回復です。特定のマップを理解させたいなら、サブクラスでそのマップ向けに書きます —— g.map_data で名前を調べられ、g.jass で任意の関数を呼べます。ここはまさに、あなた自身が決めるために残してある部分です。