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 |
Весь сервер |
Во всех них вы читаете значение как обычное поле:
local locked = Entity(vehicle).state.lockedЗапись с сервера
На сервере установка значения реплицирует его клиентам, которые видят этот bag:
-- 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'Вы можете использовать либо стиль поля, либо явный вызов, который также позволяет выбрать, реплицируется ли значение:
Entity(veh).state:set('locked', true, true):set(key, value, replicated) принимает ключ, значение и логическое значение. С true значение отправляется на другую сторону, с false оно остаётся на той стороне, которая его написала.
Совет: значения могут быть числами, строками, логическими значениями и таблицами. Таблица копируется при установке, поэтому изменение поля в таблице, которую вы прочитали, ничего не делает. Установите всю таблицу заново, чтобы обновить её.
Запись с клиента
Клиент может писать в своё состояние игрока и сущности, которыми он управляет, но простое присваивание остаётся локальным:
-- client
LocalPlayer.state:set('inService', true, true) -- replicated to the server
LocalPlayer.state.hasMask = true -- local onlyСервер может прочитать Player(source).state.inService после этого. Рассматривайте это как ненадёжные данные: модифицированный клиент может написать любое значение. Используйте написанное клиентом состояние для удобства, например для флагов UI, и держите всё с преимуществом (деньги, работа, разрешения) на сервере.
GlobalState — противоположность: сервер пишет его, каждый клиент читает его, и клиенты не могут изменить его для других.
-- client
local weather = GlobalState.weatherРеагирование на изменения
Вместо опроса значения в цикле зарегистрируйте обработчик, который выполняется при изменении ключа. Это главная причина использовать state bags на клиенте:
-- 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, когда закончите.
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 вкусов и эффектов.Смотреть скрипт →