Radio Station Framework
Integrating rs_radio with other resources
Section titled “Integrating rs_radio with other resources”Other resources — TVs, venues, jukebox props, business MLOs — can put the city radio on their own speakers by owning a static emitter programmatically. A static emitter is a fixed point (or interior) that plays a configured station with the same distance falloff, interior muffling and mixing every other radio in the city uses.
All integration exports are server-side and live on the rs_radio
resource. There are no permissions involved: exports are resource-to-resource
calls and are trusted like any other export.
Exports
Section titled “Exports”createStaticEmitter(spec) -> id | nil, error
Section titled “createStaticEmitter(spec) -> id | nil, error”Creates a persistent static emitter and returns its numeric id.
local id = exports.rs_radio:createStaticEmitter({ label = "paddock_tv_3", -- your stable name for lookup (max 80 chars) pos = { x = 215.2, y = -1002.4, z = 29.3 }, -- or a vector3 stationId = "south", -- any station id configured on this server radius = 20.0, -- 5.0 .. 250.0 volume = 0.4, -- 0.0 .. 1.0 interiorOnly = true, -- true: audio stays inside the interior enabled = false -- start switched off (default true)})Returns nil, "invalid_spec" for bad input, or
nil, "static_emitters_disabled" when the server has disabled the feature.
Emitters persist across restarts exactly like admin-placed ones, so create once and reuse — see the idempotent pattern below.
setStaticEmitterEnabled(id, enabled) -> boolean
Section titled “setStaticEmitterEnabled(id, enabled) -> boolean”The toggle. Instantly switches the emitter’s audio on or off for every
nearby player. Returns false if the id does not exist.
setStaticEmitterStation(id, stationId) -> boolean
Section titled “setStaticEmitterStation(id, stationId) -> boolean”Retunes the emitter to another configured station (a station picker in your own UI, for example).
removeStaticEmitter(id) -> boolean
Section titled “removeStaticEmitter(id) -> boolean”Deletes the emitter permanently.
findStaticEmitterByLabel(label) -> id | nil
Section titled “findStaticEmitterByLabel(label) -> id | nil”Looks up an emitter you created earlier by its exact label.
The idempotent TV pattern
Section titled “The idempotent TV pattern”Create on first use, look up on every start after that, toggle at will:
-- server-side, in your resourcelocal emitterIds = {}
local function ensureEmitter(key, coords) local label = ("mytv_%s"):format(key) local id = exports.rs_radio:findStaticEmitterByLabel(label) or exports.rs_radio:createStaticEmitter({ label = label, pos = coords, stationId = "south", radius = 18.0, volume = 0.4, interiorOnly = true, enabled = false }) emitterIds[key] = id return idend
-- wire this to your panel's "Radio" buttonRegisterNetEvent("mytv:setRadioMode", function(key, coords, on) local id = emitterIds[key] or ensureEmitter(key, coords) if id then exports.rs_radio:setStaticEmitterEnabled(id, on == true) endend)Practical notes:
- While your radio mode is on, stop your own screen audio for that TV — two mixes fighting sounds broken. rs_radio handles all playback, sync, falloff and interior acoustics for the emitter on its own.
stationIdmust match a station configured on the host server (server owners manage stations in the Station Creator). An unknown id falls back to the default station.- Wrap calls in a
GetResourceState("rs_radio") == "started"check if your resource should also run on servers without the radio. - Admin-placed and integration-created emitters share one list; server admins can see and manage yours with the usual emitter admin tools.
Removable car radios
Section titled “Removable car radios”Servers that model stolen, salvaged or mechanic-installed car radios can make
the vehicle radio conditional. Turn the gate on in config.lua:
Config.RequireInstalledVehicleRadio = trueOff (the default) every vehicle behaves as it always has, so switching this on is the only thing that changes behaviour.
Installs are keyed by number plate, not network id, so they survive the vehicle despawning, the server restarting and whichever garage script you run. rs_radio never needs to know your garage or mechanic resource exists.
The check is server-authoritative and runs where the vehicle emitter is switched on, so a client cannot talk its way past it. Switching a radio off is always allowed — otherwise stealing a radio would strand the emitter on.
Three ways to answer
Section titled “Three ways to answer”Checked in this order; the first one that has an opinion wins.
1. rs_radio’s own store (persistent)
Section titled “1. rs_radio’s own store (persistent)”The “fit it once and it stays fitted” model. Call it from your mechanic job:
-- server-side, when the mechanic finishes the installexports.rs_radio:setVehicleRadioInstalled(plate, true, mechanicIdentifier)
-- when a thief rips it outexports.rs_radio:setVehicleRadioInstalled(plate, false)Accepts a plate string or a vehicle network id. Persists to
rs_radio_vehicle_radios, created automatically on first use.
exports.rs_radio:clearVehicleRadioInstalled(plate)Forgets the vehicle entirely and hands the answer back to the state bag or your export.
2. An entity state bag (no configuration)
Section titled “2. An entity state bag (no configuration)”If you already track the radio on the vehicle, just set it server-side:
Entity(vehicle).state:set("rsRadioInstalled", true, true)3. An export of your own
Section titled “3. An export of your own”For servers whose answer already lives in their own database:
Config.VehicleRadioAccessExport = { resource = "my_mechanic", name = "HasInstalledRadio"}rs_radio calls it server-side as HasInstalledRadio(netId, plate, entity) and
expects a boolean. Results are cached per plate for 30 seconds, so a busy
street will not turn into a query storm — call
setVehicleRadioInstalled or clearVehicleRadioInstalled when your own
state changes and the cache clears immediately.
Reading the current answer
Section titled “Reading the current answer”For a mechanic UI, or to debug a report:
local installed, reason = exports.rs_radio:isVehicleRadioInstalled(plateOrNetId)reason is one of gate_disabled, rs_radio_store, state_bag,
access_export or no_record, which tells you which of the three answered.
What players see
Section titled “What players see”A denied toggle notifies with the notify.vehicle_radio_not_installed locale
key, translatable in shared/locales.lua like every other message.
With the gate on and nothing claiming a vehicle, the answer is “no radio”. If you want the opposite default — every car has a radio unless someone says otherwise — leave the gate off and remove radios from specific vehicles in your own script instead.
Inventory bridge
Section titled “Inventory bridge”Boombox and Walkboy items are given and taken through bridge/inventory.lua,
which is shipped editable (not escrowed). Built in: ox_inventory,
qb-inventory, qs-inventory, and the bare framework item functions.
Config.InventoryBridge in config.lua:
| Value | Behaviour |
|---|---|
"auto" (default) | first running one of ox_inventory, qb-inventory, qs-inventory; otherwise framework |
"ox_inventory", "qb-inventory", "qs-inventory", "framework" | force that adapter |
"custom" | use the custom adapter you filled in at the bottom of bridge/inventory.lua |
The custom adapter needs addItem(src, item, count), removeItem(src, item, count)
and getItemCount(src, item); add registerUsableItem(item, callback) only if
your inventory does not forward usable items to the framework. The console
prints [rs_radio] inventory bridge: <name> on start so you can see which one
is active.