FiveM state bags: Entity(entity).state, GlobalState и обработчики

Как работают state bags в FiveM: Entity(entity).state, Player(source).state, LocalPlayer.state, GlobalState, :set, обработчики изменений и когда их предпочесть событиям

Вы хотите поделиться небольшим объёмом данных между сервером и каждым клиентом, например что автомобиль заблокирован, что игрок скован наручниками или текущая погода, и вы продолжаете писать события, которые срабатывают при каждом изменении и выходят из синхронизации для игроков, присоединившихся позже. State bags — встроённый ответ на эту проблему.

В этой статье рассматриваются типы state bags, как их читать и писать, как реагировать на изменения и когда событие остаётся лучшим выбором.

Что такое state bag

State bag — это хранилище в виде таблицы ключей и значений, прикреплённое к чему-либо. Runtime синхронизирует его для вас, включая игроков, которые подключились после установки значения. FiveM предоставляет три типа:

Bag Читать и писать с помощью Находится на
Сущность Entity(entity).state Сущность (пешеход, автомобиль, объект)
Игрок Player(source).state на сервере, LocalPlayer.state на клиенте Игрок
Сервер GlobalState Весь сервер

Во всех них вы читаете значение как обычное поле:

lua
local locked = Entity(vehicle).state.locked

Запись с сервера

На сервере установка значения реплицирует его клиентам, которые видят этот bag:

lua
-- server
local veh = CreateVehicle(`adder`, 215.0, -810.0, 30.7, 90.0, true, true)
Entity(veh).state.locked = true

Player(source).state.cuffed = true

GlobalState.weather = 'RAIN'

Вы можете использовать либо стиль поля, либо явный вызов, который также позволяет выбрать, реплицируется ли значение:

lua
Entity(veh).state:set('locked', true, true)

:set(key, value, replicated) принимает ключ, значение и логическое значение. С true значение отправляется на другую сторону, с false оно остаётся на той стороне, которая его написала.

Совет: значения могут быть числами, строками, логическими значениями и таблицами. Таблица копируется при установке, поэтому изменение поля в таблице, которую вы прочитали, ничего не делает. Установите всю таблицу заново, чтобы обновить её.

Запись с клиента

Клиент может писать в своё состояние игрока и сущности, которыми он управляет, но простое присваивание остаётся локальным:

lua
-- client
LocalPlayer.state:set('inService', true, true)  -- replicated to the server
LocalPlayer.state.hasMask = true                -- local only

Сервер может прочитать Player(source).state.inService после этого. Рассматривайте это как ненадёжные данные: модифицированный клиент может написать любое значение. Используйте написанное клиентом состояние для удобства, например для флагов UI, и держите всё с преимуществом (деньги, работа, разрешения) на сервере.

GlobalState — противоположность: сервер пишет его, каждый клиент читает его, и клиенты не могут изменить его для других.

lua
-- client
local weather = GlobalState.weather

Реагирование на изменения

Вместо опроса значения в цикле зарегистрируйте обработчик, который выполняется при изменении ключа. Это главная причина использовать state bags на клиенте:

lua
-- AddStateBagChangeHandler(keyFilter, bagFilter, handler)
AddStateBagChangeHandler('locked', nil, function(bagName, key, value, reserved, replicated)
    local entity = GetEntityFromStateBagName(bagName)
    if entity == 0 then return end

    SetVehicleDoorsLocked(entity, value and 2 or 1)
end)
  • keyFilter — это ключ, который вас волнует, или nil для каждого ключа.
  • bagFilter ограничивает bag, например определённый player bag, или nil для всех.
  • bagName выглядит как entity:1234, player:5 или global. GetEntityFromStateBagName преобразует entity bag в дескриптор, а GetPlayerFromStateBagName делает то же самое для player bag.

На клиенте сущность может ещё не существовать, когда срабатывает обработчик, например для автомобиля, который ещё загружается. GetEntityFromStateBagName может вернуть 0 в этом случае, поэтому проверьте это. Повторное чтение состояния, когда сущность загружается, — безопасная привычка.

Та же функция существует на сервере, где она полезна для реагирования на письмо клиента репликованного значения.

State bags или события

Используйте state bag, когда Используйте событие, когда
Значение описывает текущее состояние чего-либо (заблокировано, скован, на работе) Что-то произошло один раз (покупка, удар, нажатие кнопки)
Игроки, которые присоединились позже, должны увидеть значение Только игроки, присутствующие сейчас, должны знать
Многие игроки должны прочитать его в любое время Один игрок должен быть информирован о чём-то
Вы иначе опрашивали бы в цикле Вам нужно передать результат или одноразовую нагрузку

Хорошее правило: события — для действий, state bags — для состояния. Если игрок, присоединившийся в половине, должен был бы получить то же самое снова, это состояние. См. событиям клиента и сервера для другой стороны.

Частые ошибки

  • Использование его для больших или быстро меняющихся данных. Каждое изменение отправляется по сети. Не записывайте позицию в state bag каждый кадр.
  • Доверие к записям клиента. Проверьте всё, что клиент установил, прежде чем действовать на сервере.
  • Ожидание срабатывания обработчика до того, как сущность существует. Проверьте дескриптор и прочитайте состояние снова, когда появится сущность.
  • Мутация таблицы на месте. Чтение state.items, добавление к ней и отсутствие повторной установки ничего не меняет.
  • Забывчивость сброса. Состояние сущности исчезает с сущностью, но состояние игрока или GlobalState остаётся до тех пор, пока вы его не очистите, поэтому установите его обратно на nil, когда закончите.
lua
Player(source).state.cuffed = nil

Чеклист

Симптом Исправление
Клиент устанавливает значение, но сервер никогда его не видит Используйте :set(key, value, true), чтобы оно было реплицировано
Обработчик выполняется, но дескриптор сущности — 0 Сущность ещё не загружена: проверьте и прочитайте состояние позже
Поздние присоединившиеся пропускают значение Используйте state bag вместо события
Изменение таблицы не синхронизируется Установите всю таблицу снова с помощью :set
Невозможно изменить GlobalState с клиента Только сервер пишет его: отправьте событие и дайте серверу установить его
Старое значение остаётся после завершения работы Установите ключ на nil

Короткие ответы

Что такое state bag в FiveM?

Хранилище ключей и значений, прикреплённое к сущности, игроку или всему серверу. Значения, которые вы устанавливаете, синхронизируются между сторонами игрой автоматически, поэтому вам не нужно срабатывать событие при каждом изменении.

Может ли клиент изменить state bag так, чтобы сервер это увидел?

Только если значение установлено с включённой репликацией, например LocalPlayer.state:set('key', value, true). Простое присваивание на клиенте остаётся локальным, и сервер никогда не должен доверять этому для чего-то важного.

Требуют ли state bags OneSync?

State bags сущностей зависят от OneSync, который текущие серверы используют по умолчанию. State игрока и GlobalState — более простые варианты для начала.

Скрипты без этой проблемы

Parrots as PetsЧетырнадцать птиц, которые сидят на плече, летают с вами и возвращаются по зову.Смотреть скрипт →Advanced BoostingУгон машин на заказ через планшет: контракты от класса D до S+, команды и живая очередь.Смотреть скрипт →Hookah SystemУстанавливаемые кальяны и лаунж-мебель: более 50 вкусов и эффектов.Смотреть скрипт →

Читайте также