RPG-компаньон
ИИ-компаньон для игрока в RPG и пользовательских картах: ходит за вами, помогает бить крипов, лечит, когда у вас мало здоровья, и разговаривает с вами. Четыре режима; унаследуйте один класс, поменяйте пару атрибутов — и компаньон уже ваш.
Не только для обычных матчей. В RPG и пользовательских картах можно завести себе ИИ-компаньона: он ходит за вами, помогает бить крипов, лечит, когда у вас мало здоровья, а когда делать нечего — перекидывается с вами парой фраз. Реплики можно брать даже из локальной LLM.
Как этим пользоваться — решаете вы. Всё разбито на три слоя API, снизу вверх, и любой из них можно использовать напрямую:
| Слой | Что это | Для чего |
|---|---|---|
JASS-канал g.jass | Все функции JASS, доступные авторам карт (их 1291), вызываются напрямую по имени (создать юнита, настроить союз, выдать предмет, переименовать, показать текст, воскресить героя…) | Придумать свой геймплей |
| Удобные методы | g.spawn, g.set_alliance, g.player_slots, g.show_text, g.map_data: частые задачи уже обёрнуты | Писать свои вспомогательные скрипты |
| Фреймворк компаньона | openwar3.companion.Companion + openwar3.talk.Talk: унаследуйте класс, поменяйте пару атрибутов — и у вас компаньон, который ходит следом, помогает в бою, лечит и болтает | Просто нужен компаньон |
Только для одиночных игр и игр по локальной сети, созданных вами. Создание юнитов, настройка союзов и тому подобное — это одностороннее изменение мира на вашем компьютере: в одиночной игре (против компьютера) это нормально, а в многопользовательской у остальных игроков начнётся рассинхронизация. Поэтому в многопользовательской игре JASS-канал пропускает только функции для чтения, а компаньон автоматически переходит в режим «только разговор».
Быстрый старт: в Farsight в один клик
- Выберите карту: в Farsight откройте страницу «Экземпляры и запуск» → «Следующий матч» → карта и выберите RPG-карту (в списке всё, что лежит в
ScenarioиDownloadпапкиMapsв каталоге игры, например(4)WarChasers). - Выберите схему: в выпадающем списке «ИИ-схема» на карточке экземпляра выберите Пример компаньона (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 видит, что «матч идёт, а игровые часы всё ещё на нуле», он сам нажимает пробел (g.press_to_continue() — только отправляет окну игры сообщение о нажатии клавиши и не перехватывает фокус).
Пишем своего компаньона
from openwar3.companion import Companion
from openwar3.talk import Talk
class MyBuddy(Companion):
mode = "ally" # режим, см. таблицу ниже
unit = "Hpal" # кого создать: любой четырёхсимвольный код, в том числе заданный картой
nickname = "Светик"
heal = ("holybolt", "AHhb", 0.55) # (приказ заклинания, способность для изучения, лечить, когда здоровье хозяина ниже этой доли); None = не лечить
follow_distance = 350
talk = Talk(persona="Жизнерадостный юный паладин, любит подбадривать хозяина")
Четыре режима
| mode | Кто компаньон | Пояснение |
|---|---|---|
ally (по умолчанию) | Занимает свободный слот игрока и становится вашим союзником | Свой цвет и имя (в таблице счёта и на панели союзников показывается nickname); командовать им вы не можете, он воюет сам. Союз и общий обзор фреймворк настраивает автоматически |
own | Создаётся под вашим управлением | Вы в любой момент можете командовать им вручную; когда вы им не занимаетесь, за вас им управляет ИИ |
adopt | Берёт под контроль юнита, который уже есть на карте | Переопределите adopt(g), чтобы он возвращал этого юнита (питомца или спутника, которого вам даёт карта) |
voice | Юнита не создаёт, только говорит | Болтовня и напоминания; мир не меняет, поэтому работает и в многопользовательской игре |
Если свободного слота нет, ally автоматически переходит в own; в многопользовательской игре или если юнита создать не удалось — в voice.
Компаньон в режиме ally ориентируется на «лучшего юнита в этом слоте прямо сейчас» (герои в приоритете), а не держится за одного конкретного юнита. В реальной проверке одна карта приняла компаньона за живого игрока, удалила паладина и выдала ему героя карты — компаньон просто взял этого героя под контроль и изучил способности, которые ему задала карта. Если герой погибает, компаньон по возможности воскрешает его на месте; если карта воскрешает героя сама — продолжает им пользоваться.
Что он делает каждый тик
Проверяет условия по порядку и выполняет первое сработавшее:
| Порядок | Действие | Условие | Настройка |
|---|---|---|---|
| 1 | Отступление | Своё здоровье ниже 25% и рядом враги: отойти за спину хозяина | retreat_at |
| 2 | Лечение | Здоровье хозяина ниже порога, способность перезарядилась, расстояние не больше 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 (хозяин щёлкнул по компаньону правой кнопкой), а также методы бота on_start / on_tick / on_event / on_end. Сколько раз компаньон лечил, помогал в бою, убивал, следовал, отступал, говорил и воскрешал, записывается в self.stats и выводится в конце.
Как к нему обратиться
- Команды чата: наберите в чате
-follow(за мной),-stay(стой здесь),-heal(вылечи сейчас) или-hi(поздороваться). Список команд задаётся вcommands, реакции меняются переопределениемon_command. - Правый клик по компаньону: вызывает
on_poke. В примере реакция такая: если у хозяина не полное здоровье — подлечить, иначе сказать что-нибудь. - Диалог с портретом: приветствие, гибель хозяина, повышение уровня хозяина и возвращение компаньона звучат через встроенный в игру диалог с портретом (портрет внизу меняется на компаньона, на экране появляются субтитры); остальное — облачком над головой.
- Панель состояния: панель в левой части экрана с полоской здоровья компаньона, тем, чем он сейчас занят, настроением (радость / азарт / тревога / страх / грусть), числом убийств и лечений. Она рисуется на холсте, поэтому безопасна и в многопользовательской игре.
Реплики и локальные LLM
Talk подбирает реплики по событиям и показывает их облачком над головой; в режиме voice или когда облачко показать нельзя — в левом нижнем углу экрана. Каждая реплика также пишется в лог схемы, так что потом можно посмотреть, что он говорил.
| Событие | Когда | Событие | Когда |
|---|---|---|---|
hello | Только появился | master_low | У хозяина мало здоровья |
poke | Хозяин щёлкнул по нему правой кнопкой | master_levelup | Хозяин повысил уровень |
fight | Начался бой | master_died / master_back | Хозяин пал / воскрес |
kill | Убил крипа (называет его имя) | buddy_low / buddy_died / buddy_back | У самого компаньона мало здоровья / он пал / вернулся |
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-карт в основном созданы самой картой (четырёхсимвольные коды вроде 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) не содержат стандартных файлов данных объектов — тогда имена читаются из текстовых данных карты. В проверке на этом компьютере все 38 RPG- и пользовательских карт разобраны успешно, в 37 из них найдены имена юнитов.
Поделиться как схемой
Компаньон — это обычный подкласс openwar3.Bot, так что его можно оформить как ИИ-схему и поделиться с другими. В манифест добавляются ещё два поля:
{"id": "my-buddy", "name": "Мой компаньон", "entry": "my_buddy.py", "fair": false, "judge": false}
"fair": false — нужно, чтобы пользоваться JASS-каналом (создавать юнитов, настраивать союзы); "judge": false — не определять победу и поражение по правилам обычного матча.
Результаты проверки
2026-09-24, тестовый экземпляр, карта WarChasers, скорость 2×:
- Все 18 проверок JASS-канала пройдены: слоты игроков, преобразование юнитов в дескрипторы и обратно, вещественные возвращаемые значения, строковые аргументы, создание юнита в свободном слоте, настройка союза, переименование, удаление юнита; вызов из полосы игрока и неверное число аргументов корректно отклоняются.
- Компаньон: сам прошёл экран «Нажмите любую клавишу» → появился рядом с хозяином и поздоровался → вслед за хозяином зашёл в круг выбора героя, получил от карты героя и взял его под контроль → следовал за хозяином (на расстоянии 200 ~ 400) → бил крипов, после убийства сказал «Красиво!» → при малом запасе здоровья отступил → погиб, был воскрешён картой и продолжил следовать за хозяином.
Что ещё не сделано
- Произвольный текст, который игрок пишет в чат, прочитать нельзя. Фиксированные команды чата уже работают; чтобы компаньон по-настоящему свободно болтал с вами, нужно получать сам текст.
- Компаньон не понимает геймплей конкретной карты (задания, магазины, сюжет). Он умеет общее: следовать, помогать в бою, лечить. Чтобы он понимал конкретную карту, опишите её в своём подклассе —
g.map_dataподскажет имена,g.jassвызовет любую функцию. Именно эта часть оставлена на ваше усмотрение.