Skip to content

Мета-слой хоста ​

Часть хоста, которую наблюдает автор игры: как движок управляет Wasm-ядром игры и разбирает то, что ядро сообщает, и поверх этого — мета-слой движка: реестр участников, игровые правила, которыми владеет движок (раунды, счёт, команды, ротация карт, зрители), менеджеры и модули. Игра параметризует их через gameConfig; заменить их она не может.

Вызовы ядра и события ​

Движок управляет Wasm-ядром игры и разбирает то, что ядро сообщает (сам ABI — в plugin-api.md):

  • Жизненный цикл. Карта грузится через load_map (приходит уже отмасштабированной, поэтому ядро грузит её с scale: 1). Человек входит через spawn_actor и уходит через remove_actor; scripted-участник (бот) — spawn_scripted_actor/remove_scripted_actor; смена данных игрока зовёт reset_actor. На слоёной (2.5D) карте точка респауна может называть свой уровень ([x, y, angle, level]), и движок передаёт его вызовом set_actor_level(gameId, level) — вызов определяется по наличию, старое ядро без него выводит уровень из геометрии.
  • Ввод. Ввод игрока попадает в ядро через apply_input; ядро подтверждает номер последовательности в player-блоке кадра.
  • События. После каждого step движок вычерпывает take_events() и сам разбирает стандартный словарь: panelSet/panelActive обновляют панель (field — собственный ключ схемы панели игры), death уходит в отчёт об убийстве (счёт, исход раунда), shake трясёт камеру. custom — единственный тип с игровым смыслом: он как есть передаётся в необязательный HostPlugin.onCoreEvent(data, { panel, vimp }). Ядро работает с числовыми id (u32), мета-слой — со строковыми ключами; движок приводит их на этой границе.
  • Кадры. Снапшот упаковывается через pack_body/pack_frame; первый кадр входящего игрока строится из players_data() ядра.

Мета-слой ​

Мета-слой движка ведёт матч поверх событий ядра. Код, который плагин добавляет в хост (обработчики команд, модули), обязан использовать только изоморфные API — см. overview.md.

Список карт, по которому комната ротируется, — актуальный на момент старта комнаты список каталога.

Участники ​

Люди и scripted-участники (боты) делят один реестр и одно числовое пространство id; различаются геттерами isScripted/isNetworked, не по формату id. У каждого участника есть gameId, name, model, team, teamId и status; имя scripted-участника — scripted.namePrefix плюс его id из конфига игры.

Игровые правила, которыми владеет движок ​

Эти правила живут в движке и не заменяются; игра параметризует их через gameConfig либо реализует альтернативу целиком внутри своего WASM-ядра. Детали по менеджерам — ниже.

  • Раунды. Раунд заканчивается, когда команда уничтожена (каждый участник, люди и scripted, не жив), а выжившие остались не больше чем в одной другой команде; при трёх и более командах он идёт, пока живые есть только в одной. Победитель — команда, у которой остались живые, как бы ни погиб последний игрок (убийство противником, огонь по своим, самоуничтожение, окружение, уже вышедший убийца) — каждое событие death запускает проверку уничтожения. Уничтожение без выживших где-либо (оба последних игрока погибли одновременно или игра с одной командой) завершает раунд без победителя: каждый игрок получает реплику defeat (наблюдатели — victory) и сообщение о конце раунда без победившей команды. Истечение таймера раунда не завершает раунд результатом — оно просто начинает новый без изменения счёта. Следующий раунд стартует через timers.roundRestartDelay. Игре без раундов ближе всего один долгий раунд (большой roundTime, респауны в ядре) или одна команда, в которой уничтожение невозможно.
  • Счёт. Убийство противника: у убийцы score +1 и очки ранга +1, у жертвы deaths +1 и status = 'dead'. Огонь по своим: у убийцы score −1 и очки −1, у жертвы deaths +1. Самоубийство: у жертвы только deaths +1. Убийца, уже покинувший игру: фрага нет, жертва всё равно гибнет, раунд всё равно разрешается. Голове каждой уничтоженной команды — deaths +1 один раз за раунд, а в конце раунда голове выжившей команды — score +1 (при ничьей — нет). Правило ±1 пишется синхронно с отчётом об убийстве, хука для его изменения нет. Погибший игрок становится наблюдателем за убийцей до следующего раунда.
  • Реплики звука отсюда: frag — убийце, death — жертве, victory — победившей команде и наблюдателям (при любом исходе), defeat — всем остальным в конце раунда. Существует ровно пять движковых событий (roundStart, victory, defeat, frag, death); gameConfig.soundCues сопоставляет их именам из звукового конфига игры, озвученные таким образом звуки обходят лимит мировых голосов, несопоставленная реплика не отправляется, а {} их отключает.
  • Команды. gameConfig.teams сопоставляет имена команд числовым id и включает команду наблюдателей; при noSpectators: true в нём ровно одна команда, и входящий человек сразу попадает в неё (без голосования). Одна играющая команда — допустимая конфигурация. Вместимость команды — respawns[team].length на текущей карте. Вход в полную команду вызывает scripted.removeOneForHuman(team); если он вернул false, игрок получает TEAMS_TEAM_FULL и остаётся на месте. Смена команды в течение timers.teamChangeGracePeriod от старта раунда бесплатна; позже сменивший команду погибает и наблюдает до следующего раунда. Падение ниже 2 активных людей сбрасывает статистику и сразу начинает новый раунд — если только игра не объявила endlessRound: true, который отключает все инициированные движком перезапуски раунда (это правило, уничтожение команды и таймер раунда); /nr и смена карты по-прежнему его перезапускают.
  • Ротация карт. Истечение timers.mapTime запускает системное голосование, предлагающее mapsInVote карт из каталога; игрок может предложить карту и через меню голосований (единственное предложение применяется сразу); переключение ждёт timers.mapChangeDelay. Смена карты пересоздаёт scripted-участников, сбрасывает панель и таблицу статистики, очищает голосования и сбрасывает ранг и состояние на платформу.
  • Наблюдатели. Погибшие игроки и члены команды наблюдателей следят за другим участником (камера после гибели следует за убийцей); spectatorKeys (nextPlayer/prevPlayer, из набора клавиш 0) переключают наблюдаемого; курсор скрывается после 3 с без движения мыши.
  • Игровые сообщения. Коды зафиксированы движком (vimp-engine/src/config/gameCodes.js): winnerTeam → индекс 0gameInform.list, roundStart → 1, gameOver → 2. Индексы позиционные — перестановка массива меняет смысл; код 1 заодно запускает анимацию панели/логотипа на клиенте. Технические сообщения (techInformList, с движковыми умолчаниями) индексируются, как перечислено в plugin-api.md; плейсхолдеры {0}, {1} берутся из массива params.
  • Начальное голосование. gameConfig.initialVote (обычно 'teamChange') — голосование, отправляемое игроку сразу после его первого кадра; null кидает прямо в игру.
  • Профиль игрока. rank — целое число, принадлежащее движку, в кросс-игровом формате, рисуется лобби платформы; state — собственный JSON-блоб игры, стартующий с playerState.defaultState, читается и пишется через объект vimp в onCoreEvent и сбрасывается на конце раунда, смене карты и уходе. Оба разнесены по (пользователь, gameId); поскольку хост — недоверенный браузер, оба технически подделываемы — известное ограничение P2P-модели.
  • Кики. Голосования за кик и эндпоинта /ban нет. Защита от плохого хоста — миграция хоста движка: автоматические триггеры (перегрузка, скрытая вкладка, сетевой лаг) и голосование /changehost, которое считает платформа, а не хост (только режим лобби). Ничего из этого не проходит через плагин, и в меню голосований игры для него нет пункта. Пороги и коды закрытия: game-config.md и snapshot.md.

Менеджеры и модули, которыми управляет игра ​

Игра не заменяет менеджеры и модули движка; она управляет ими через gameConfig и хуки плагина:

КомпонентЧто поставляет играПоведение, видимое игре
RoundManagerteams, респауны карт, scripted, timers.*, endlessRoundраунды, команды и ротация карт ровно как в правилах выше
CommandProcessorчат-команды через HostPlugin.chatCommands (registerCommand(name, handler))собственных команд у движка нет; обработчик — handler(ctx, gameId, args); неизвестная команда отвечает «Command not found»; при нескольких активных людях команда может пройти через голосование
VoteCoordinator, Voteигровые голосования (createVote), их текстыкулдаун темы timeBlockedVote, время жизни voteTime; больше 7 вариантов листаются; ничья решается случайным выбором; голосование, созданное при открытом, ждёт в очереди
Panelpanel.fields, activeKeyHUD по пользователям; уходят только изменения, раз за тик снапшота; авторитетные значения (здоровье, боезапас) живут в ядре и попадают на панель через его события
Statсхема stat (колонки, агрегация, команды)строки таблицы и итоги команд; дельта за тик, полное состояние при входе
ChatHostPlugin.systemMessages (группы кодов игры) и, на клиенте, их текстыу групп движка s/v/m/c есть английские умолчания; игровой код без текста молча отбрасывается; каждое системное сообщение — код, данные идут в params
TimerManagertimers.*игровой цикл, таймеры раунда и карты, RTT-пинги, проверки простоя, отложенные рестарт раунда и смена карты
RTTManagerrtt.*, idleKickTimeoutзамер задержки и кики, см. snapshot.md
PlayerDataSyncplayerState.defaultStaterank/state по участникам, см. plugin-api.md

← Предыдущая: Rust-ядро движка (vimp-engine-core) · Следующая: Клиентская среда для авторов игр →

vimp-engine 0.38.1 · vimp-engine-core 0.23.2 · create-vimp-game 0.5.4