Реплики и локальные модели
Любой юнит в игре может заговорить от любого имени — в облачке над головой. Подключите локальную LLM: фраза на входе, ответ появляется над юнитом.
Облачки — зрелищный слой: на исход игры они не влияют и подходят для стримов, комментирования и отладки.
- говорить может любой юнит от любого имени; несколько юнитов могут говорить одновременно;
- размер шрифта, цвет, ширина, хвостик, прозрачность и скорость печати настраиваются для каждого облачка отдельно;
- можно напрямую подключить локальную LLM (LM Studio) с потоковым выводом: облачко обновляется по мере генерации.
Из бота
Проще всего — встроенный в SDK метод say:
g.say(hero, "За мной, в атаку!", seconds=4)
Запуск и интерфейс
Проще всего — главная страница Farsight «Центр управления»: сначала нажмите «Локальная LLM → Запустить и загрузить модель» (локальный сервер LM Studio + загрузка настроенной модели в видеопамять), затем «Облачка реплик → Запустить». На карточках можно смотреть логи, останавливать и перезапускать.
Интерфейс — на странице «Облачка реплик» в левой панели Farsight: заставить юнита говорить (выбрать юнита, написать текст, настроить стиль, поговорить с моделью), посиделки крестьян, диалоги в кадре, реакция на события, настройки модели; всё применяется к экземпляру, выбранному в верхней панели.
Можно и из командной строки:
python speech/speak_launch.py # запустить сервис локальной модели + загрузить и прогреть модель + поднять API облачков
python speech/speak_launch.py --restart # перезапустить API после изменения кода
python speech/speak_launch.py --stop # остановить API и выгрузить модель из видеопамяти
Каждый шаг работает по принципу «уже запущено — пропустить», поэтому повторный запуск ничего не ломает.
HTTP API
По умолчанию http://127.0.0.1:8872/ (порт задаётся в ports.speech файла openwar3.json); вызывать его может любая программа.
Заставить юнита говорить POST /api/say
{
"inst": 16,
"bubbles": [
{ "unit": "0x14A12614", "name": "Горный король", "text": "За мной, в атаку!" },
{ "unit": "0x14A12924", "name": "Архимаг", "text": "Сейчас будет снежная буря.",
"style": { "font_px": 26, "text_color": "#FFE080", "bg_color": "#C0102040" } },
{ "screen": [960, 110], "key": 1, "name": "Рассказчик", "text": "Первая волна орков прибудет через 30 секунд.",
"style": { "tail": false, "type_ms": 0 } },
{ "world": [-4684, 2644], "key": 2, "text": "Точка сбора", "style": { "font_px": 16 } }
]
}
| Поле | Описание |
|---|---|
unit / world / screen | Одно из трёх: следовать за юнитом (если у него есть полоска здоровья — прямо над ней) / координаты карты / пиксели экрана (для закадрового текста) |
name | Имя говорящего в первой строке — любое, не обязательно имя этого юнита |
text | Текст реплики, переносится автоматически |
duration_ms | Сколько показывать; 0 = автоматически 3 ~ 5 секунд |
key | Номер облачка в мире / на экране: новое сообщение с тем же key заменяет старое |
update | Если такое облачко уже есть — заменить только текст, не сбрасывая таймер (для потокового вывода) |
style | font_px, max_width_px, text_color, bg_color, border_color, tail, side_px, opacity, type_ms, font… |
Одновременно — не больше 32 облачков; накладные расходы — в среднем около 0.1 ~ 0.2 ms за кадр.
Разговор с локальной моделью POST /api/chat
{
"inst": 16, "unit": "0x14A12614", "name": "Горный король",
"persona": "Ты играешь Мурадина, Горного короля из Warcraft: прямолинейный, любит выпить. Одна-две разговорные фразы, не длиннее 40 символов.",
"message": "Впереди толпа огров. Атакуем или нет?",
"stream": true
}
Возвращается {"reply": "...", "first_token_ms": 283, "total_ms": 342}, и к этому моменту ответ уже висит над этим юнитом. Каждый юнит помнит последние 6 обменов репликами.
Прочее
| Метод | Описание |
|---|---|
GET /api/instances | Запущенные игры |
GET /api/units?inst=16&mine=true&heroes=true | Список юнитов (с китайскими названиями, координатами, здоровьем) |
POST /api/clear | Убрать одно или все облачка |
GET /api/llm, POST /api/llm | Посмотреть / изменить настройки модели (base_url, model, max_tokens, temperature) |
POST /api/banter | Посиделки крестьян: рабочие на базе по очереди ворчат в образе своих персонажей, плюс объявление в начале матча (все данные о ходе игры — реальные) |
POST /api/camtalk | Диалоги в кадре: герои и их свита, попавшие в кадр, разговаривают в соответствии со своими ролями |
POST /api/events | Реакция на события: начало боя, конец боя, гибель героя, переход на следующий тир, потеря здания… говорят, только когда что-то случилось |
Как выбрать локальную модель
Замеры на одной RTX 5090 (5 игровых реплик):
| Модель | Видеопамять | Скорость | Одна реплика | Вывод |
|---|---|---|---|---|
| Qwen3.6-35B-A3B (MoE, за раз активно только 3B), Q4, thinking выключен | 20.6 GB | ≈142 token/s | ≈0.3 s (первый токен ≈0.27 s) | Рекомендуем: быстро, естественно отыгрывает роли на китайском |
| gpt-oss-20b (MXFP4), reasoning low | 11.3 GB | ≈280 token/s | 0.3 ~ 0.8 s | Если мало видеопамяти; китайский чуть более пресный |
| Qwen3.6-27B (плотная), Q4 | 17.2 GB | ≈39 token/s | через 5.5 s всё ещё думает | Не подходит для диалога в реальном времени |
- Скорость определяется числом параметров, активных за раз, а не общим числом: MoE на 35B активирует только 3B и работает в 3 ~ 4 раза быстрее плотной 27B.
- «Размышления» обязательно выключить: иначе все токены уходят на размышления, и до ответа дело не доходит.
- Облачко печатает около 22 символов в секунду, так что скорость генерации — уже не узкое место; по-настоящему на ощущения влияет задержка первого токена.
Чтобы реплики звучали «по-настоящему»
Передавайте модели только реальные данные о ходе игры (число матчей, победы и поражения, численность армии, запасы) и явно пишите «используй только эти факты». Проверено: без этого ограничения модель выдумывает бои, которых не было.