RPG コンパニオン
RPG / カスタムマップで、プレイヤーに AI の相棒をつけます。あなたについて歩き、敵との戦いを手伝い、HP が減れば回復し、話し相手にもなります。4 つのモードがあり、クラスを 1 つ継承していくつかの属性を変えるだけで、あなただけのコンパニオンになります。
対戦だけではありません。RPG やカスタムマップでは、自分に AI の相棒をつけられます。あなたについて歩き、敵との戦いを手伝い、HP が減れば回復してくれて、手が空いているときは話しかけてきます —— セリフはローカル LLM につなぐこともできます。
どう使うかはあなた次第です。 この機能は 3 層の API に分かれていて、下の層から上の層まで、どれも直接使えます。
| 層 | 内容 | 向いている用途 |
|---|---|---|
JASS チャネル g.jass | マップ作者が使える 1291 個の JASS 関数を、名前で直接呼び出します(ユニット作成、同盟設定、アイテム付与、名前変更、テキスト表示、ヒーロー蘇生……) | 自分で遊び方を作りたい |
| 便利 API | g.spawn、g.set_alliance、g.player_slots、g.show_text、g.map_data:よく使う処理をまとめてあります | 自分用の補助スクリプトを書く |
| コンパニオンフレームワーク | openwar3.companion.Companion + openwar3.talk.Talk:継承していくつかの属性を変えるだけで、追従、援護、回復、会話をこなす相棒になります | 相棒がほしい |
ローカル、または LAN で自分で立てたゲーム専用です。ユニット作成や同盟設定のような操作は、自分のマシンが一方的にワールドを変更するものです。シングルプレイ(コンピューターとの対戦)なら問題ありませんが、マルチプレイでは他のプレイヤーと同期がずれます。そのためマルチプレイでは、JASS チャネルは読み取り専用の関数だけを許可し、コンパニオンは自動的に「話すだけ」に切り替わります。
最速で始める:Farsight でワンクリック起動
- マップを選ぶ:Farsight の「インスタンス」ページ →「次のゲームの設定」→ マップで、RPG マップを 1 つ選びます(ゲームディレクトリの
Maps以下にあるScenario、Download内のマップがすべて一覧に出ます。例:(4)WarChasers)。 - スキームを選ぶ:インスタンスカードの「AI スキーム」ドロップダウンで コンパニオンのサンプル(buddy) を選び →「選択」。
- テストを開始する:ゲームが起動したら、あなた自身がゲームウィンドウでプレイします。コンパニオン —— 「ヒカリ」という名前のパラディン —— があなたのそばに現れます。
コマンドラインからも起動できます。
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 が減って撤退 → 戦死後にマップによって蘇生され、再び追従。
まだできていないこと
- プレイヤーが入力した任意のチャット文は読み取れません。決まったチャットコマンドはすでに使えます。コンパニオンと本当に自由に会話するには、入力されたテキストそのものを取得する必要があります。
- コンパニオンは個々のマップの遊び方(クエスト、ショップ、ストーリー)を理解していません。できるのは汎用的な追従、援護、回復です。特定のマップを理解させたいなら、サブクラスでそのマップ向けに書きます ——
g.map_dataで名前を調べられ、g.jassで任意の関数を呼べます。ここはまさに、あなた自身が決めるために残してある部分です。