Bridges
np_hud talks to frameworks and voice resources only through Hud.Framework and Hud.Voice (client side). Each bridge file calls Hud.Bridge.shouldLoad(kind, name, resource) and returns early unless its resource is started and selected (Config.Framework / Config.Voice, or 'auto'). Files are numbered so auto prefers qbx over qb (qbx also provides qb-core) and pma-voice over the others. The 9_* files are fallbacks: standalone and none claim the slot when nothing else loaded, so the HUD always starts.
Optional exports are called through pcall, so a missing or broken dependency degrades instead of erroring. Detection runs once when the resource starts, so start optional resources before np_hud in server.cfg.
The server side has no framework bridge. It only counts players (GlobalState.np_hud_players) and routes notifications.
Framework (client), bridge/framework/client/
Contract: name, caps { money, job, hunger, thirst, stress }, isLoaded(), isDead(), get() → { cash, bank, job = { name, label, grade }, hunger, thirst, stress }, onLoaded(cb), onUnloaded(cb), onChange(cb). client/modules/framework.lua re-reads get() on every onChange, sends only the diff, and also refreshes every Config.Intervals.world as a safety net.
| Contract | qbx (qbx_core) | qb (qb-core) | esx (es_extended) | standalone |
|---|---|---|---|---|
| data | exports.qbx_core:GetPlayerData() | QBCore.Functions.GetPlayerData() | ESX.GetPlayerData() | – |
| cash / bank | money.cash / money.bank | same | accounts[name='money'/'bank'].money | not shown |
| job | job.label, job.grade.name | same | job.label, job.grade_label | not shown |
| hunger / thirst | player state bag hunger/thirst, hud:client:UpdateNeeds, metadata.* | hud:client:UpdateNeeds, metadata.* | esx_status:onTick (percent), only if esx_status is started | not shown |
| stress | state bag stress, hud:client:UpdateStress, metadata.stress | hud:client:UpdateStress, metadata.stress | esx_status entry stress if a server adds one | not shown |
| change events | QBCore:Player:SetPlayerData, needs events, state bag handlers | QBCore:Player:SetPlayerData, needs events | esx:setAccountMoney, esx:setJob, esx_status:onTick | – |
| isLoaded | LocalPlayer.state.isLoggedIn / load event | same + citizenid | ESX.IsPlayerLoaded() | NetworkIsPlayerActive |
| isDead | metadata.isdead / inlaststand, LocalPlayer.state.isDead | metadata.isdead / inlaststand | esx:onPlayerDeath / esx:onPlayerSpawn, PlayerData.dead | ped only |
| onLoaded / onUnloaded | QBCore:Client:OnPlayerLoaded / QBCore:Client:OnPlayerUnload | same | esx:playerLoaded / esx:onPlayerLogout | – |
The HUD stays hidden (hud:visible { reason = 'loading' }) until isLoaded() returns true.
Voice (client), bridge/voice/client/
Contract: name, get() → { range 1..3, label, radio, radioTalking, muted }, isTalking(), onChange(cb). client/modules/voice.lua refreshes on every onChange and polls every Config.Intervals.voice (200 ms), because pma-voice and none only expose talking through NetworkIsPlayerTalking.
| Contract | pma-voice | SaltyChat (saltychat) | YACA (yaca-voice) | none |
|---|---|---|---|---|
| range | LocalPlayer.state.proximity.index (+ .mode as label), event pma-voice:setTalkingMode | SaltyChat_VoiceRangeChanged(range, index0, count) | yaca:external:voiceRangeUpdate(range) mapped to an index via exports['yaca-voice']:getVoiceRanges() | fixed 2 |
| talking | NetworkIsPlayerTalking | SaltyChat_TalkStateChanged(isTalking) | yaca:external:isTalking(state) | NetworkIsPlayerTalking |
| radio channel | LocalPlayer.state.radioChannel (0 = off, set server-side by pma-voice) | SaltyChat_RadioChannelChanged(channel, isPrimary) (primary only), initial exports.saltychat:GetRadioChannel(true) | yaca:external:setRadioFrequency(channel, frequency) + yaca:external:changedActiveRadioChannel(channel), '0' = left | – |
| radio talking | LocalPlayer.state.radioActive / pma-voice:radioActive | SaltyChat_RadioTrafficStateChanged(_, primaryTransmit, _, secondaryTransmit) | yaca:external:isRadioTalking(state, channel) | – |
| muted | – | SaltyChat_MicStateChanged(muted) | yaca:external:microphoneMuteStateChanged(state) | – |
More than three voice ranges are spread over the HUD's three bars (ceil(index / count * 3)).
Notes on the YACA API
- YACA has no
yaca:external:setRadioChannelevent. The documented events areyaca:external:setRadioFrequency(channel, frequency) andyaca:external:changedActiveRadioChannel(channel), and the bridge uses those.yaca:external:muteStateChangedis deprecated in favour ofyaca:external:microphoneMuteStateChanged.
Fuel, client/modules/vehicle.lua
Config.Vehicle.fuel = 'auto' picks the first started resource: ox_fuel (Entity(veh).state.fuel), LegacyFuel (exports.LegacyFuel:GetFuel), cdn-fuel (exports['cdn-fuel']:GetFuel), ps-fuel (exports['ps-fuel']:GetFuel), otherwise GetVehicleFuelLevel. Errors fall back to the native per call.
Sources (checked 2026-10-04)
- pma-voice: https://github.com/AvarianKnight/pma-voice (main):
client/commands.lua(proximitystate bag,pma-voice:setTalkingMode),client/module/radio.lua(pma-voice:radioActive,radioActivestate bag),server/module/radio.lua(Player(src).state.radioChannel),docs/client-getters/events.md - SaltyChat: https://github.com/saltminede/saltychat-fivem/blob/master/README.md (Events / Exports, client)
- YACA: https://github.com/yaca-systems/fivem-yaca-typescript/blob/main/README.md (Events, client exports
getVoiceRange,getVoiceRanges,getActiveRadioChannel) - qb-core: https://github.com/qbcore-framework/qb-core:
client/functions.lua(QBCore.Functions.Notify(text, type, length, icon)),client/events.lua(QBCore:Notify,QBCore:Player:SetPlayerData) - qbx_core: https://github.com/Qbox-project/qbx_core:
client/functions.lua(Notify→lib.notify) - ESX Legacy: https://github.com/esx-framework/esx_core:
[core]/es_extended/client/functions.lua(ESX.ShowNotification(message, notifyType, length, title, position)→esx_notify) - ox_lib: https://github.com/overextended/ox_lib:
resource/interface/client/notify.lua(lib.notify, net eventox_lib:notify)
Not verified against a running server
- The qbx_core player state bags
hunger/thirst/stress. They're read when present and the bridge falls back to metadata and thehud:client:*events. hud:client:UpdateNeeds/hud:client:UpdateStressare sent by qb-smallresources and other qb scripts, not by qb-core itself. Servers without them still update throughQBCore:Player:SetPlayerData.- The SaltyChat
indexis 0-based according to the README, and the bridge adds 1. - Minimap: the
SETUP_HEALTH_ARMOURvalue used to restore the bars on stop (0), and the minimap component math (see Development → Capability notes).