State: statebags and sync¶
mi_core publishes live player state on statebags and a few server-wide numbers on GlobalState. A statebag is replicated by the server, so any resource reads it with zero network cost and no export call. This is how a resource knows a player's job, duty, or login state without polling the core.
There are two ways state reaches you:
- Statebags for a value tied to one player (their job, whether they are on duty) or to the whole server (the player count).
- PlayerData sync events for the full player-data object, sent as small per-key deltas.
Player statebags¶
Read with Player(source).state.<key> on the server, or LocalPlayer.state.<key> on the owning client. React to changes with AddStateBagChangeHandler('<key>', ...).
| Statebag | Value | Set when | Read by |
|---|---|---|---|
citizenid |
string | login | resources that key off the character id from a statebag |
isLoggedIn |
boolean | true at login, false at logout |
ox_inventory, mi-gps |
job |
the full job table | login, SetJob, SetJobDuty |
read via PlayerData or the delta events, below |
onduty |
boolean | login, and whenever duty or job changes | mi-bossmenu, mi-dispatch, mi-policejob |
gang |
the full gang table | login, SetGang |
read via PlayerData or the delta events |
paycheck |
number, the accrued paycheck balance | on accrual and on collect | the paycheck collectors |
drunk |
number 0 to 100 | on drink and decay | mi-hud |
loadInventory |
true |
once, at login, after the player object exists | ox_inventory (this is its signal to register the player's inventory) |
instance |
number, the routing bucket | on SetPlayerBucket |
bucket-aware resources |
Two of these are worth calling out because resources depend on them existing:
onduty. qbx_core signals duty only through theQBCore:Client:SetDutyevent, but several mi resources watch the bag directly (mi-bossmenu tracks duty-time sessions, mi-dispatch decides group membership). mi_core mirrorsjob.ondutyinto a discreteondutybag so those keep working. If more than one resource needs to watch a piece of state, mi_core sets it as a bag even when qbx_core did not.loadInventory. ox_inventory's qbx bridge registers a player's inventory only insideAddStateBagChangeHandler('loadInventory', ...). mi_core sets it once at login, after the player object exists, so the inventory sets up. Without it the player cannot open their inventory.
GlobalState¶
Server-wide numbers, replicated to every client. Read with GlobalState.<key>.
| Key | Value | Read by |
|---|---|---|
PlayerCount |
loaded-player count | mi-hud |
PVPEnabled |
PvP on or off, from rules.pvp |
any resource gating friendly fire |
dutyCounts |
{ [job] = onDutyCount } for every job |
mi-jail |
_job:<name> |
on-duty count for one job, for example GlobalState['_job:police'] |
mi-ambulancejob, mi-damages, mi-drugs |
Use these for counts. Do not scan the player map to count on-duty players (see golden rule 8 on the Server exports page). The counters are kept live with plus-or-minus-one bumps on login, duty toggle, job change, and logout.
Resource statebags¶
Beyond the core, several mi resources publish their own statebags so the rest of the ecosystem can react to a feature with no export and no network round trip. Each resource's own page documents its bags. This is the combined map. Read them the same way: Player(source).state.<key> on the server or LocalPlayer.state.<key> on the owning client, and AddStateBagChangeHandler('<key>', ...) to react.
Player and vehicle bags:
| Statebag | Scope | Published by | Meaning |
|---|---|---|---|
hidehud |
player | any resource (menus, scripted scenes) | While true, mi-hud hides the whole HUD. Set it around a fullscreen NUI or a cutscene, then clear it. |
seatbelt |
player | mi-hud | Player is belted. |
harness |
player | mi-hud | Player has a race harness on. |
vehicleLock |
vehicle | mi-keys (also mi-robbery, mi-policejob) | Lock state of a vehicle. |
plate |
vehicle | mi-keys | The visible plate after a fake-plate swap (the key still checks the identity plate). |
mi_holster |
player | mi-holster | The player's holster and concealed-carry state. |
miJail |
player | mi-jail | Player is serving a sentence. |
miSedatedUntil |
player | mi-surgery | Timestamp until which a patient stays sedated. |
mcJobStyles |
player | mi-chat | The player's job style (label and color) for chat. |
stance |
player | mi-emotes | Current walk or crouch stance. |
isInEmote |
player | mi-emotes | Player is playing an emote. |
UICustoms |
player | mi-customsgarage | The customs menu is open (used to hold the HUD and camera). |
miShop |
player | mi-appearance | Player is inside a clothing shop. |
aliasId |
player | mi-allcards | Active fake-identity alias, if any. |
isDead |
player | mi-ambulancejob | Player is dead or downed (the medical death contract). |
isHandcuffed |
player | mi-policejob | Player is cuffed. Part of the restraint contract (restraint, isBeingDragged, isBeingCarried, canUseWeapons). |
gsr |
player | mi-policejob | Gunshot residue on the player, read when swabbing for evidence. |
miNewPlayer |
player | mi-newplayer | New player is under anti-troll protection. |
missing_<organ> |
player | mi-organs | Which organs are missing (for example missing_kidney). Read by mi_core needs, mi-hud stress, and medical resources. |
miNumber |
player | mi-phone | The player's active phone number. |
vehicleid |
vehicle | mi-garage | Database id of a persisted vehicle. Read by the ox_inventory bridge for the trunk and glovebox stash. |
persisted |
vehicle | mi-garage, mi_core | Marks a vehicle as saved (persisted) rather than temporary. |
Server-wide bags on GlobalState:
| Key | Published by | Meaning |
|---|---|---|
miDonateLimited |
mi-donate | Flags a limited-stock donation item. |
PlayerData sync¶
When a player's data changes (money, metadata, a job field), the change reaches the owning client as a small delta, not the whole object. This keeps a hunger tick or a money change to about fifty bytes on the wire instead of the roughly two kilobytes of a full PlayerData copy.
What mi_core fires on a change:
- Server side,
QBCore:Player:SetPlayerDataas a localTriggerEventwith the wholePlayerData. For server resources that listen for it. It does not cross the network. - To the owning client, the delta only:
qbx_core:client:onUpdatePlayerData(key, value)for a top-level key, orqbx_core:client:onSetMetaData(metadata, oldValue, value)for a metadata branch.
On the client, mi_core applies the delta to its PlayerData in place, then re-broadcasts QBCore:Player:SetPlayerData locally (no network) so every resource and bridge that already hooks that event keeps firing exactly as before.
So there are two ways to react on the client, both valid:
-- 1. The classic way. Still works: mi_core re-broadcasts it locally after every delta.
RegisterNetEvent('QBCore:Player:SetPlayerData', function(data)
-- data is the full, current PlayerData
end)
-- 2. The efficient way. React only to the key you care about.
RegisterNetEvent('qbx_core:client:onUpdatePlayerData', function(key, value)
if key == 'job' then -- the job changed
end
end)
RegisterNetEvent('qbx_core:client:onSetMetaData', function(meta, old, value)
if meta == 'hunger' then -- hunger changed
end
end)
Do not hook AddEventHandler for these on the client. They are net events, so use RegisterNetEvent.