Pular para o conteúdo
State bags, OneSync and entity ownership

State bags, OneSync and entity ownership

Sync data the FiveM way: GlobalState, Player and Entity state bags, change handlers, replication rules, OneSync entity ownership, server side entities and routing buckets.

Por enquanto, esta documentação está em inglês.

Last updated

With OneSync on, the server knows about every player and entity and decides which clients see what. On top of that, FiveM gives you state bags: key/value data attached to the server, a player or an entity, synced to clients automatically. Together they replace a lot of hand written event syncing.

Plays from youtube-nocookie.com after you click.

OneSync in two minutes

  • Enabled with set onesync on in server.cfg. Every current framework requires it.
  • The server tracks all networked entities. Clients only receive entities near them: the culling radius is 424 units around each player.
  • Each networked entity has an owner: the client that simulates it (usually the closest or the one who created it). The owner sends updates, the server forwards them.
  • The server can read entity state (GetEntityCoords(GetPlayerPed(src)), GetVehicleNumberPlateText) and create entities.
  • Routing buckets split the world into separate dimensions.
  • Up to 48 slots free, up to 2048 with the right Cfx.re tier.

State bags

There are three kinds, all with the same API:

Bag Where Who can write (default)
GlobalState Server wide Server only
Player(serverId).state (server) / LocalPlayer.state (client) One player The server, or that player’s client
Entity(entity).state One networked entity The server, or the entity’s owner

Set and read

server.lua
GlobalState.weather = 'RAIN'
GlobalState.maintenance = false

Player(src).state:set('job', 'police', true)          -- true = replicate to clients

local veh = CreateVehicleServerSetter(`police3`, 'automobile', 441.0, -1019.0, 28.5, 90.0)
Entity(veh).state:set('fuel', 100.0, true)
client.lua
print(GlobalState.weather)                           -- 'RAIN'
print(LocalPlayer.state.job)                         -- 'police'

local veh = GetVehiclePedIsIn(PlayerPedId(), false)
if veh ~= 0 then
    print(Entity(veh).state.fuel)
end

Setting with .key = value uses the default replication: values set by the server replicate to clients, values set by a client do not replicate. state:set(key, value, replicated) lets you choose. The server can also keep private data on a bag with replicated = false.

React to changes

client.lua
AddStateBagChangeHandler('fuel', nil, function(bagName, key, value, _reserved, replicated)
    local entity = GetEntityFromStateBagName(bagName)
    if entity == 0 then return end
    print(('Fuel of %d is now %.1f'):format(entity, value))
end)
  • The second argument filters by bag name (nil for all bags). Bag names look like player:12, entity:345 or global.
  • GetEntityFromStateBagName and GetPlayerFromStateBagName turn a bag name back into a handle.
  • The entity may not exist yet on this client when the handler fires, so check for 0.

Rules and limits

  • Flat keys only. Nested writes like Entity(x).state.car.fuel = 5 don’t sync, the getters and setters are naive. Use Entity(x).state['car:fuel'] or set the whole table again.
  • Keep values small. Every replicated change goes to every client that can see the bag.
  • Security: by default a client can write its own player bag and bags of entities it owns (not replicated unless it chooses to). If your resources only set state from the server, set sv_stateBagStrictMode true so clients can’t write at all. Never trust a value a client could have written.

When to use what

You want Use
A value everyone can read, changes rarely (weather, server flags) GlobalState
Per player data other scripts or clients need (job, duty, radio channel, is dead) Player(src).state
Data that belongs to a car or object (fuel, locked, owner, siren mode) Entity(ent).state
A one off action (“play this sound now”) An event
A request with an answer A callback
Large or private data Server memory or database, send only what’s needed

Entity ownership

  • NetworkGetEntityOwner(entity) (server) returns the server ID of the client that owns it.
  • Ownership moves as players move. Scripts that change an entity (freeze it, set its velocity) should do it on the owner, or on the server with the server setters.
  • Before changing a networked entity on a client that doesn’t own it, request control: NetworkRequestControlOfEntity(entity) and wait for NetworkHasControlOfEntity(entity). It can fail or take time.
  • SetEntityOrphanMode (server) controls what happens to an entity when its owner leaves, so server created vehicles don’t vanish.

Creating entities on the server

With OneSync the server can create entities, which is safer than trusting clients to spawn things:

server.lua
RegisterNetEvent('garage:takeOut', function(plate)
    local src = source
    -- ...check that src owns this car and is near the garage...
    local coords = vector4(215.0, -810.0, 30.7, 157.0)
    local veh = CreateVehicleServerSetter(`sultan`, 'automobile', coords.x, coords.y, coords.z, coords.w)
    while not DoesEntityExist(veh) do Wait(0) end
    SetVehicleNumberPlateText(veh, plate)
    Entity(veh).state:set('owner', GetPlayerIdentifierByType(src, 'license'), true)
    TriggerClientEvent('garage:enter', src, NetworkGetNetworkIdFromEntity(veh))
end)

The client then gets the entity from the network ID:

client.lua
RegisterNetEvent('garage:enter', function(netId)
    local veh = NetworkGetEntityFromNetworkId(netId)
    local timeout = GetGameTimer() + 3000
    while not DoesEntityExist(veh) and GetGameTimer() < timeout do
        Wait(0)
        veh = NetworkGetEntityFromNetworkId(netId)
    end
    if DoesEntityExist(veh) then
        SetPedIntoVehicle(PlayerPedId(), veh, -1)
    end
end)

CreateVehicleServerSetter is the recommended server vehicle native (it takes the vehicle type, like automobile, bike, heli). CreateVehicle, CreatePed and CreateObjectNoOffset also exist on the server. If you enable sv_entityLockdown strict, the server is the only place entities can be created, so this pattern becomes required.

Routing buckets

A routing bucket is a separate world. Players and entities in different buckets can’t see or interact with each other. Bucket 0 is the default.

server.lua
-- put a player in their own apartment instance
SetPlayerRoutingBucket(src, 1000 + src)
SetRoutingBucketPopulationEnabled(1000 + src, false)  -- no ambient peds and traffic there
SetRoutingBucketEntityLockdownMode(1000 + src, 'strict')

-- back to the main world
SetPlayerRoutingBucket(src, 0)

Use cases: character selection, apartments and instanced interiors, races, events, admin testing. Entities created by a player in a bucket stay in that bucket (SetEntityRoutingBucket moves them).

Plays from youtube-nocookie.com after you click.

Common mistakes

  • Expecting a client written state value to show up on other clients: it doesn’t unless replicated, and the server should be the writer anyway.
  • Nested table writes to state bags (see limits above).
  • Keeping entity handles across machines: send network IDs.
  • Deleting or changing an entity you don’t own and wondering why it comes back.
  • Forgetting that players in another routing bucket are invisible to each other, then debugging “sync bugs”.

Next: NUI basics.