Headless client (HC) - это вторая копия серверного бинарника Arma 3, запущенная с -client: она заходит на ваш сервер как невидимый игрок и обсчитывает ИИ, который ей отдаёт миссия. Помогает он, только когда выполняются три условия: время кадра сервера съедает именно ИИ, миссия даёт в лобби слот для headless client и что-то - код миссии, headless-компонент ACE или другой скрипт - действительно передаёт ему группы ИИ. Сделайте всё это правильно, и персистентная миссия с сотнями ИИ будет держать играбельный FPS сервера до глубокой ночи; ошибитесь в чём-то одном, и вы потратите память и долю CPU на процесс, который сидит в слоте и ничего не делает.
Серверные переключатели (headlessClients[], localClient[]) разобраны в руководстве по server.cfg для Arma 3. Эта статья - обо всём вокруг них: почему вся суть в локальности, как добавить слот в миссию, три способа переноса ИИ, запуск нескольких HC и как доказать, что это сработало.
Локальность: почему headless client вообще может помочь#
Каждый объект в мультиплеерной сессии Arma 3 локален ровно для одной машины. Эта машина обсчитывает его симуляцию - для ИИ это поиск пути, выбор целей, обновление знаний о противнике, решения о стрельбе - и рассылает результаты всем остальным. Все прочие машины держат удалённую копию, которую они рисуют и интерполируют, но за которую не думают.
По умолчанию ИИ, расставленный в редакторе или созданный серверными скриптами, локален для сервера. Значит, главный поток сервера думает за каждого бота в миссии в дополнение ко всему остальному, что он делает, а симуляция в Arma в основном однопоточная. Когда ИИ становится больше, FPS сервера падает, а когда падает FPS сервера, всё становится хуже одновременно: ИИ реагирует с опозданием, техника дёргается, у игроков начинается рассинхрон.
Headless client - это просто ещё одна машина, для которой ИИ может быть локальным. Это полноценный клиент Arma без рендеринга и звука, поэтому всё его время кадра уходит на симуляцию юнитов, которыми он владеет. Перенесите на него сотню ботов, и эти сто юнитов перестанут стоить серверу чего-либо, кроме сетевых обновлений.
Из этой модели следуют три вывода, которые объясняют большинство проблем с HC:
- ИИ не переезжает сам. Юнит локален там, где он был создан, пока что-нибудь не вызовет для него
setGroupOwner. Подключение HC ничего не меняет в уже существующем ИИ. - Локальность задаётся на уровне группы. Переносятся группы целиком, а вместе с группой переезжают её юниты, точки маршрута и техника, которой они управляют.
- Скрипты выполняются там, где выполняются. Код на сервере, который командует юнитом, переехавшим на HC, может перестать работать, потому что многие команды действуют только там, где юнит локален.
Стоит ли оно того? Измерьте, прежде чем строить#
Headless client не бесплатен. Это второй процесс Arma с тем же списком модов, поэтому ему нужна своя память - обычно от 2 до 4 ГБ при умеренном наборе модов - и своё время CPU. Если проблема вашего сервера не в ИИ, он ничего не даст.
Сначала измерьте. Зайдите админом и выполните #monitor 5 - эта команда каждые пять секунд выводит в чат FPS сервера и связанные цифры. Затем сравните:
| Что вы видите | Что это значит | Поможет ли HC? |
|---|---|---|
| FPS сервера падает с ростом числа ИИ и восстанавливается, когда ИИ гибнет | Нагрузку даёт ИИ | Да |
| FPS сервера медленно падает часами при стабильном числе ИИ | Утечка в скриптах или накопление объектов | Нет, чините миссию |
| FPS сервера в норме, игроки жалуются на лаги | Сеть или производительность клиентов | Нет |
| FPS сервера плохой с первой минуты при малом числе ИИ | Нагрузка от модов или скриптов при старте | Нет, профилируйте миссию |
| Память почти на пределе, FPS в норме | Дело в памяти, а не в ИИ | Нет, и HC сделает с памятью хуже |
Общая версия этого разбора - в статьях что на самом деле означает tick rate и CPU или RAM для игровых серверов. В панели с графиком CPU характерная картина такая: серверный процесс упёрся в свой лимит CPU, память ровная, а миссия спавнит ИИ.
Серверная часть, коротко#
Два массива в server.cfg заставляют сервер правильно обращаться с HC:
headlessClients[] = {"127.0.0.1"};localClient[] = {"127.0.0.1"};headlessClients[] перечисляет адреса, которым разрешено занимать слоты headless client. localClient[] помечает адреса как локальные, что снимает ограничения пропускной способности, рассчитанные на игроков с домашним интернетом, - без этого HC душится как обычный игрок, и обновления ИИ встают в очередь. Если HC работает на другой машине, в обоих массивах нужен адрес этой машины вместо loopback.
HC запускается тем же бинарником:
$ ./arma3server_x64 -client -connect=127.0.0.1 -port=2302 \ -password="joinpassword" -profiles=hc1 -name=hc1 \ -mod="@cba_a3;@ace" -world=empty -nosoundЧто делает каждая часть:
-connectи-portуказывают на игровой порт сервера.-password- это пароль для входа, если он есть на сервере. Не админский пароль.-modдолжен в точности совпадать с клиентским списком модов сервера. Элементы из-serverModне нужны.-profilesи-nameдают HC собственную папку профиля и лог, чтобы его файл.rptне смешивался с серверным.
HC подключается, появляется в лобби и занимает слот headless client, если он есть в миссии. Если слота нет, он висит на экране выбора слотов.
Как добавить слот в миссию#
Именно к этому шагу сводится большинство жалоб "мой HC ничего не делает". В миссии должна быть сущность headless client, и она должна быть игровой (playable).
В редакторе Eden headless client - это логическая сущность Headless Client. Поставьте по одной на каждый HC, который собираетесь запускать, и для каждой:
- Задайте имя переменной -
HC1,HC2,HC3. По этому имени скрипты находят HC. - Отметьте Playable. Неигровая сущность HC никогда не предлагается как слот.
- Сохраните и экспортируйте миссию в мультиплеер, чтобы
.pboвmpmissionsеё содержал.
HC занимает слот автоматически при подключении; назначать его никому не нужно. В скриптах имя переменной затем указывает на объект HC, а owner HC1 (вычисленное на сервере) возвращает его client ID - то самое число, которое нужно setGroupOwner.
Крупные миссии сообщества, такие как Antistasi и Liberation, поставляются с уже расставленными слотами headless client. Миссия из Workshop, в описании которой headless client не упоминаются, почти наверняка их не содержит.
Три способа перенести ИИ на headless client#
Когда слот есть, что-то должно передать ИИ. Подходов три, и их можно сочетать.
1. Сразу создавать ИИ на HC
Самый чистый подход для миссий, которые вы пишете сами: выполняйте код спавна на самом HC, чтобы ИИ был локален там с рождения и никогда не переезжал.
// true only on a headless clientif (!hasInterface && !isServer) then { [] execVM "scripts\spawnPatrols.sqf";};// fall back to the server if no HC is connectedif (isServer && {isNil "HC1" || {isNull HC1}}) then { [] execVM "scripts\spawnPatrols.sqf";};hasInterface ложно на выделенных серверах и headless client; isServer истинно только на сервере. Это сочетание и определяет HC. Запасной вариант важен: миссия, которая спавнит ИИ только на HC, останется без противников в тот вечер, когда HC не сможет подключиться.
2. Передавать группы с сервера через setGroupOwner
ИИ, расставленный в редакторе или созданный существующими серверными скриптами, нужно передавать. setGroupOwner выполняется только на сервере, принимает группу и client ID и переносит группу вместе с её юнитами.
[] spawn { // wait until the HC has connected and has a client ID waitUntil { sleep 5; !isNil "HC1" && {!isNull HC1} && {owner HC1 > 2} }; sleep 30; // let mission init finish first private _hc = owner HC1; { private _grp = _x; if (local _grp && {({isPlayer _x} count units _grp) == 0} && {!(_grp getVariable ["keepOnServer", false])}) then { _grp setGroupOwner _hc; }; } forEach allGroups;};Самое важное в этом цикле - проверки. Передавайте только группы, локальные для сервера, никогда не трогайте группу, в которой есть игрок, и оставьте себе способ исключить группы, которые должны остаться на месте, - заскриптованные сцены, юниты с привязанной серверной логикой. Задержка даёт завершиться остальной инициализации; передача группы посреди настройки - классический источник наполовину настроенного ИИ.
3. Поручить это моду
ACE3 включает headless-компонент, который автоматически передаёт группы ИИ подключённым headless client и перераспределяет их, когда HC подключаются или уходят. Он настраивается в настройках аддонов CBA: включите его, задайте задержку передачи и решите, что делать при отключении HC. У него есть переменная исключения для групп и юнитов, которые должны оставаться на сервере; точное её имя для вашей версии смотрите в документации ACE. Для модовой группы, которая уже играет с ACE, это путь с наименьшими усилиями, и он работает с ИИ, расставленным в редакторе и созданным через Zeus.
Старые миссии используют Werthles' Headless Module - модуль редактора, который делает ту же работу. Выберите один автоматический механизм на миссию; две системы, переносящие одни и те же группы, будут драться друг с другом.
Что ломается при смене владельца ИИ#
Смена локальности не проходит для скриптов незаметно. Типичные проблемы:
- Команды с локальным эффектом. Некоторые команды работают только там, где юнит локален. Серверный цикл, вызывающий
doMove,disableAIилиsetBehaviourдля группы, которая переехала на HC, может молча ничего не делать. Выполняйте такой код там, где живёт группа, например черезremoteExecс цельюgroupOwner, или запускайте весь скрипт поведения на HC. - Обработчики событий. Обработчики, добавленные через
addEventHandler, локальны для машины, которая их добавила, и некоторые срабатывают только там, где юнит локален. Серверный обработчикKilledилиHitна переданном юните может перестать срабатывать. Используйте мультиплеерные варианты (addMPEventHandlerсMPKilled,MPHit) или добавляйте обработчики на машине-владельце. - Настройки, применённые до передачи. Часть состояния ИИ, заданного на сервере до переноса, его не переживает. Применяйте навыки,
disableAIи поведение после передачи, на новом владельце. - Трупы и очистка. Сборка мусора по-прежнему работает на сервере. Это нормально, но скрипт очистки, который проверяет
localперед удалением, пропустит юниты, принадлежащие HC.
Если миссия ведёт себя странно только при подключённом HC, эти четыре пункта - ваш чек-лист.
Несколько headless client и динамическая симуляция#
Один HC - это второй поток. Несколько HC - несколько потоков, и миссии с очень большим числом ИИ распределяют группы между HC1, HC2 и HC3 - по районам, по сторонам или по кругу. Каждому нужен свой игровой слот, свой процесс со своими -profiles и -name и своя доля памяти и CPU.
Больше HC помогают, только если есть ядра, на которых их запускать. На своём железе это вопрос физических ядер. На арендованном сервере CPU обычно - жёсткий лимит: в RE:NODE каждый сервер - это один контейнер, ограниченный купленной долей, - поэтому headless client внутри того же лимита конкурирует за него с сервером. Он всё равно помогает, когда сервер упёрся в один поток, а часть лимита простаивает, - а это самый частый случай, - но создать CPU, которого нет в тарифе, он не может. Второй HC в том же лимите редко оказывается лучше одного.
Дополнение к headless client - динамическая симуляция, встроенная в игру начиная с 1.74. Она замораживает группы ИИ, далёкие от всех игроков, и будит их, когда кто-то приближается:
enableDynamicSimulationSystem true;"Group" setDynamicSimulationDistance 1500;{ _x enableDynamicSimulation true } forEach allGroups;Замороженная группа почти ничего не стоит, где бы она ни была локальна. На большой карте с гарнизонами ИИ повсюду динамическая симуляция часто снимает больше нагрузки, чем headless client, а вместе они - стандартная схема для больших персистентных миссий. Включайте её для каждой группы после любой передачи.
Как доказать, что это сработало#
Не верьте, что всё работает, только потому что HC есть в лобби. Проверьте три вещи.
- Группы действительно переехали. В отладочной консоли с выполнением на сервере посчитайте группы по владельцам:
{groupOwner _x == owner HC1} count allGroups. Ноль означает, что ничего не передалось. - HC справляется. Пусть HC пишет свою частоту кадров и число групп в свой
.rpt:
if (!hasInterface && !isServer) then { [] spawn { while {true} do { diag_log format ["HC fps %1, local groups %2", diag_fps, {local _x} count allGroups]; sleep 60; }; };};- Серверу стало лучше. Выполните
#monitor 5в одной и той же точке миссии с HC и без него. Если FPS сервера не изменился, узким местом был не ИИ, и HC можно убрать.
HC, у которого обвалился собственный FPS, теперь сам стал узким местом вместо сервера: ИИ, которым он владеет, реагирует так же плохо, как раньше. Это момент, чтобы разделить нагрузку между двумя HC, сократить число ИИ или активнее использовать динамическую симуляцию. Храните логи .rpt от HC рядом с серверными; сколько и зачем, рассказано в статье логи, которые стоит хранить.
Решение проблем#
HC так и не появляется в лобби. Его адреса нет в headlessClients[], список модов не совпадает или он указывает не на тот порт. Прочитайте собственный .rpt HC - там написано, почему подключение не удалось.
HC висит на выборе слотов. В миссии нет игровой сущности headless client или все слоты HC уже заняты.
HC подключается, но ничем не владеет. ИИ ничто не передаёт. Добавьте цикл setGroupOwner, включите headless-компонент ACE или спавните ИИ на HC.
ИИ на HC стоит на месте. Точки маршрута или скрипты поведения выполняются на сервере и больше не действуют. Перенесите скрипт на владельца.
Сломался серверный учёт убийств. Обработчики событий локальны. Используйте MPKilled или добавьте обработчик на HC.
Всё стало хуже. HC подтолкнул сервер к пределу памяти или делит лимит CPU, который уже был заполнен. Проверьте оба графика и, если так, уберите его.
HC отключается, и противники исчезают. Когда HC уходит, его ИИ возвращается серверу. Если после этого серверу тяжело - это ожидаемо; если ИИ удаляется, значит, какой-то скрипт подчищает группы HC при отключении.
FAQ#
Нужна ли отдельная лицензия Arma 3 для headless client?
Нет. Headless client - это бесплатный бинарник выделенного сервера, запущенный в режиме клиента и скачанный тем же способом, что и сервер. Ему нужны те же моды, что и серверу, и больше ничего.
Сколько headless client стоит запускать?
Один, пока вы не измерили, что он перегружен. Добавляйте второй, только когда собственный FPS первого HC падает под нагрузкой и есть свободный CPU для ещё одного процесса. Большинству групп больше одного никогда не нужно.
Исправит ли headless client рассинхрон?
Только если рассинхрон вызван тем, что серверу не хватает времени кадра из-за ИИ. Сетевой рассинхрон из-за соединения игрока или нехватка памяти на сервере от этого не меняются.
Нужно ли писать миссию специально под headless client?
Ей нужен игровой слот headless client. Перенос ИИ может выполнять сама миссия, headless-компонент ACE или другой скрипт, так что миссия без кода под HC тоже может выиграть, если добавить слот и автоматический балансировщик.
Может ли headless client работать на другой машине?
Да. Впишите адрес этой машины в headlessClients[] и localClient[], откройте путь между ними и будьте готовы к небольшой дополнительной задержке в обновлениях ИИ. На той же машине проще и обычно быстрее.
Что происходит с ИИ, когда headless client падает?
Его группы автоматически возвращаются серверу. Миссия продолжается, а сервер несёт ту же нагрузку, что и до этого. Хорошая миссия или балансировщик снова передаёт их, когда HC переподключается.




Комментарии
Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.