qb-scoreboard
qb-scoreboard renders a panel showing the player count, the on-duty police count, and which
illegal activities are currently available or busy. It also draws server IDs above nearby players
who opted in.
It is a read-only display plus one shared busy flag. It does not gate any robbery itself. The robbery resources decide independently whether to run, and only some of them report their state back here. Compare with qb-hud, which shows the local player’s own status rather than server state.
The manifest at the commit linked above declares version 1.2.1.
Dependencies and start order
The manifest declares no dependency entry, but both scripts call
exports['qb-core']:GetCoreObject({ 'Functions' }) at load time, so qb-core is a hard runtime
dependency. Start qb-core first.
The server script also calls QBCore.Functions.IsOptin(source). That method exists in qb-core at
commit 9b3cddc.
On an older or forked core that lacks it, the callback errors and the panel never opens.
Configuration
config.lua is loaded as a shared_script, so both sides see the same table.
| Key | Default | Meaning |
|---|---|---|
Config.Toggle | true | true registers a single scoreboard command that toggles. false registers +scoreboard and -scoreboard, so the panel is only visible while the key is held |
Config.OpenKey | 'HOME' | Default key passed to RegisterKeyMapping. Players can rebind it in the FiveM settings menu |
Config.ShowIDforALL | false | When true, IDs are drawn above every nearby player regardless of their opt-in state |
Config.MaxPlayers | GetConvarInt('sv_maxclients', 48) | Read from your server convar, not hardcoded |
Config.IllegalActions | table | Activity key to { minimumPolice, busy, label } |
The shipped Config.IllegalActions keys are storerobbery, bankrobbery, jewellery, pacific,
and paleto, with minimum police counts of 1, 3, 2, 5, and 4. These numbers are display values for
this panel. Each robbery resource carries its own police requirement in its own config, so changing
a number here does not change what a robbery actually permits. Keep the two in sync by hand.
Commands and key mapping
With Config.Toggle = true:
RegisterCommand('scoreboard', ...)toggles the panel.RegisterKeyMapping('scoreboard', 'Open Scoreboard', 'keyboard', Config.OpenKey).
With Config.Toggle = false, +scoreboard and -scoreboard are registered instead and the mapping
targets +scoreboard. See Commands and key mappings for why the plus
and minus prefix pair behaves that way.
Server callback
QBCore.Functions.CreateCallback('qb-scoreboard:server:GetScoreboardData', function(_, cb)Returns three values to the client, in this order:
| Position | Type | Content |
|---|---|---|
| 1 | number | Total online QBCore players |
| 2 | number | Players whose job.name is exactly police and whose job.onduty is true |
| 3 | table | Keyed by player source, each entry { optin = <boolean> } |
Note the duty count matches the literal job name police only. It does not use job.type == 'leo'.
Other resources audited on this site, for example
qb-jewelery and qb-truckrobbery,
count either job.name == 'police' or job.type == 'leo'. If you run additional law enforcement
jobs, the number shown here will be lower than the number those resources actually require.
Events
| Event | Side | Arguments |
|---|---|---|
qb-scoreboard:server:SetActivityBusy | server | activity, bool |
qb-scoreboard:client:SetActivityBusy | client | activity, busy |
The server handler sets Config.IllegalActions[activity].busy and broadcasts the client event to
everyone.
Integration example
Another server script marks an activity busy while it runs:
-- server side, inside your own resource
TriggerEvent('qb-scoreboard:server:SetActivityBusy', 'bankrobbery', true)
-- later, when the heist resets
TriggerEvent('qb-scoreboard:server:SetActivityBusy', 'bankrobbery', false)TriggerEvent on the server is the correct call here. The handler is registered with
RegisterNetEvent, so it is also reachable from a client with TriggerServerEvent, and at this
commit it performs no source check and no validation of activity or bool.
Two consequences worth planning for:
- Any client can flip any activity’s busy flag for every player.
- An
activitykey that is not inConfig.IllegalActionsraises a Lua error on the server, because the handler indexes the table without checking.
If that matters on your server, wrap the event in your own guarded handler and stop broadcasting the
raw one, or validate activity against Config.IllegalActions before forwarding.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Panel does not open | The qb-scoreboard:server:GetScoreboardData callback errored. Check the server console, and confirm your qb-core build provides QBCore.Functions.IsOptin |
| Player count is right, police count is always 0 | Your law enforcement job is not literally named police, or nobody has job.onduty set |
| Max players shows 48 on a larger server | sv_maxclients is not set before the resource starts, so the convar default is used |
Client error about optin being nil | A nearby player is not present in the opt-in table returned by the callback. This happens when the panel is open while a player joins |
| Activity always shows busy | Something set the busy flag and never cleared it. Restarting the resource resets Config.IllegalActions to its file defaults |
Proposed manual smoke test
Not yet executed. On staging:
- Open and close the panel with the configured key and with
/scoreboard. - Put one player on duty as
policeand confirm the count increments. - Fire
qb-scoreboard:server:SetActivityBusyfor one activity and confirm every connected client sees it flip. - Set
Config.Toggle = false, restart, and confirm hold-to-view behaves. - Set
Config.ShowIDforALL = trueand confirm IDs draw above nearby players.