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.
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.
OneSync in two minutes
- Enabled with
set onesync oninserver.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
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)print(GlobalState.weather) -- 'RAIN'
print(LocalPlayer.state.job) -- 'police'
local veh = GetVehiclePedIsIn(PlayerPedId(), false)
if veh ~= 0 then
print(Entity(veh).state.fuel)
endSetting 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
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 (
nilfor all bags). Bag names look likeplayer:12,entity:345orglobal. GetEntityFromStateBagNameandGetPlayerFromStateBagNameturn 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 = 5don’t sync, the getters and setters are naive. UseEntity(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 trueso 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 forNetworkHasControlOfEntity(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:
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:
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.
-- 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).
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.
