Where each setting lives
| Source | Contains | Edited in | Applies |
|---|---|---|---|
config/config.lua | Network, auth, Discord, framework, inventory | Text editor | After restart tRadio |
config/sv_functions.lua | Server hooks — job→codeplug mapping, access, player names, 911 caller ID | Text editor | After restart tRadio |
config/cl_functions.lua | Client hooks — canTalk, battery, background sound detection, animations | Text editor | After restart tRadio |
data.json | Zones, channels, codeplugs, alerts, speakers, towers, geo zones, all server settings | Web admin panel | Instantly, no restart |
The rule of thumb: a setting lives in a .lua file only if it is a genuine Lua function, or if applying it binds a port or registers a command that FiveM cannot undo. Everything else is in the admin panel at http://your-server-ip:port/admin.
config.lua
Server and authentication
Config = {
serverAddress = "http://203.0.113.10:7777",
isSatelliteServer = false,
serverPort = 7777,
authToken = "<20+ random characters>",
adminPassword = "<your admin password>",
dispatchNacId = "4417",
}| Key | Default | Description |
|---|---|---|
serverAddress | "localhost" | Public IP or domain of the voice server, including the port. "" auto-detects the public IP. Behind a reverse proxy, use the full URL: "https://radio.yourdomain.com" |
isSatelliteServer | false | true on secondary game servers that federate into one shared host — see Multi-server federation |
serverPort | 7777 | TCP port for the voice server, dispatch panel, and admin panel. Must be free and open |
authToken | "CHANGE_ME" | Shared secret between the game server and the voice server. Use 20+ random characters |
adminPassword | "141" | Web admin panel password. "" disables password login (Discord auth then required) |
dispatchNacId | "141" | Passphrase dispatchers type on the dispatch panel login screen. Not related to in-game channel access |
dispatchNacId is not an access control
dispatchNacId, a codeplug's nac, and the NAC shown on a radio screen are three different
things. None of them gate which channels a player can use — that is entirely codeplugs and ACE
grants.
Discord authentication
Optional OAuth for the dispatch panel and the admin panel. Create an application at the Discord Developer Portal, then add both redirect URIs under OAuth2 → Redirects:
http://203.0.113.10:7777/radio/dispatch/auth
http://203.0.113.10:7777/admin/auth/callback
Config = {
discord = {
authEnabled = true,
clientId = "1284739201847362048",
clientSecret = "<your discord client secret>",
guildId = "938271046582910384",
roles = "938271046582910111,938271046582910222",
adminRoles = "938271046582910333",
redirectUri = "",
},
}| Key | Default | Description |
|---|---|---|
authEnabled | false | Require Discord login for the dispatch panel. Every other field is ignored when this is false |
clientId | "" | Discord application client ID |
clientSecret | "" | Discord application client secret |
guildId | "" | Your Discord server (guild) ID |
roles | "" | Comma-separated role IDs allowed into the dispatch panel. "" allows any guild member |
adminRoles | "" | Comma-separated role IDs allowed into the admin panel. "" disables Discord admin access |
redirectUri | "" | Override the OAuth redirect. Only needed behind a reverse proxy or custom domain |
Password and Discord login can both be active — the login page shows both. Dispatch sessions last 24 hours; role changes take effect at next login.
Framework detection
Config = {
framework = 'auto',
frameworkResources = {
esx = 'es_extended', qbcore = 'qb-core', qbx = 'qbx_core',
nd = 'ND_Core', ox_core = 'ox_core', nat2k15 = 'framework',
},
autoRefreshOnJobChange = true,
}| Key | Default | Description |
|---|---|---|
framework | 'auto' | 'auto', 'esx', 'qbcore', 'qbx', 'nd', 'ox_core', 'nat2k15', or 'custom' |
frameworkResources | see above | Resource names used for detection. Only change these if you renamed a framework resource |
autoRefreshOnJobChange | true | Listen for your framework's job-change event and refresh the player's codeplug immediately instead of waiting for the ~15 s poll |
'auto' probes in order: qbx → qbcore → esx → nd → ox_core → nat2k15, falling back to 'custom'. The job-change listeners live in escrow-protected server code, not in sv_functions.lua, so they survive an upgrade even if you keep a customised sv_functions.lua. Set autoRefreshOnJobChange = false only if your own code already calls exports['tRadio']:refreshNacId(source).
Inventory item requirement
Config = {
inventory = {
enabled = true,
item = 'radio',
system = 'auto',
},
}| Key | Default | Description |
|---|---|---|
enabled | false | When true, a player must carry the item to open the radio |
item | 'radio' | Item name as registered in your inventory resource |
system | 'auto' | 'auto', 'ox_inventory', 'qb', 'esx', or 'custom' |
'auto' tries ox_inventory first, then falls back to the active framework's inventory. When the item requirement is enabled, tRadio also registers the item as usable automatically for ox_inventory, QBCore, QBX, and ESX — using it from the inventory opens the radio. You do not need to write a CreateUseableItem call.
Register the item in your inventory first
tRadio never creates the item. Add it to your inventory resource's item list before enabling this.
On ox_inventory installs bridged to ESX or QBox, item use is routed before tRadio's hook is
consulted — if using the item does nothing, add client.event = "radio:client:openRadio" to the
radio entry in your items.lua.
Grant the tRadio.access ACE to let specific players or groups bypass the item check. For an unsupported inventory such as VORP, set system = 'custom' and replace Config.playerHasRadioItem in sv_functions.lua:
Config.playerHasRadioItem = function(serverId)
local item = (Config.inventory and Config.inventory.item) or 'radio'
local p = promise.new()
exports.vorp_inventory:getItemCount(serverId, item, function(count) p:resolve(count) end)
return (Citizen.Await(p) or 0) >= 1
endFramework integration
Assigning codeplugs to jobs
A player needs a codeplug to open a radio at all. Map jobs to codeplug IDs in config/sv_functions.lua. Keys must match a codeplug's id in the admin panel. Changes require restart tRadio.
config/sv_functions.lua (shipped)
local JOB_CODEPLUG_MAP = {
['police'] = 'leo',
['sheriff'] = 'leo',
['ambulance'] = 'leo',
['fire'] = 'leo',
}The lookup is case-insensitive, and works unchanged on ESX, QBCore, QBX, ND_Core, ox_core, and nat2k15 — the framework-specific job lookup is already written for you. On nat2k15, keys are dept level keys ('lspd_level', 'bcso_level') rather than job names.
Different codeplugs per rank. A value can be a table of grade thresholds instead of a plain string. The highest threshold at or below the player's grade wins:
local JOB_CODEPLUG_MAP = {
['police'] = { [0] = 'leo', [3] = 'leo-supervisor', [8] = 'leo-command' },
['fire'] = 'fire',
}Grades 0–2 get leo, 3–7 get leo-supervisor, 8+ get leo-command. A grade below the lowest key gets no codeplug — the same secure default as an unmapped job.
Unmapped jobs get no radio, on purpose
Civilians and any job not listed receive no codeplug. Returning a fallback codeplug would silently
grant that job whatever the fallback allows — and a codeplug with empty access lists resolves to
"every zone". If you do want open access, uncomment the return 'leo' line at the bottom of
Config.getPlayerCodeplugId and make sure that codeplug's lists are deliberately broad.
With framework = 'custom' and no changes, every player receives the leo codeplug.
Server hooks
All four live in config/sv_functions.lua and ship with working implementations for every supported framework. Edit them only to change behaviour.
| Hook | Returns | Called |
|---|---|---|
Config.getPlayerAccess(serverId) | boolean | On every server-side radio event. false denies the radio entirely. The shipped version checks the inventory item (when enabled) and then requires a codeplug |
Config.playerHasRadioItem(serverId) | boolean | Only when Config.inventory.enabled is true. Replace this when system = 'custom' |
Config.getPlayerCodeplugId(serverId) | codeplug ID string or nil | On access resolution. The shipped version reads JOB_CODEPLUG_MAP |
Config.getPlayerName(serverId) | string | For the name shown on radios and the dispatch panel. Falls back to extracting a callsign suffix such as 1A-12 from the server display name |
Refreshing access after a job change
With autoRefreshOnJobChange = true (the default) this is automatic on QBCore, QBX, ESX, ND_Core, and ox_core. nat2k15 has no job-change event and relies on the ~15 s poll.
To trigger it yourself from your own resource:
exports['tRadio']:refreshNacId(source)Access is re-resolved immediately and pushed to the client. A player sitting on a channel they can no longer reach is disconnected from it.
Client hooks
In config/cl_functions.lua.
Config.canTalk() — runs while PTT is held. Return false to block transmission.
Config.canTalk = function()
if IsPlayerDead(PlayerId()) then return false end
if IsPedSwimming(PlayerPedId()) then return false end
return true
endIf this starts returning false mid-transmission, the client force-stops PTT and logs canTalk check failed mid-transmission to F8. This is the most common non-hardware cause of "my mic doesn't work".
Config.batteryTick(currentBattery, deltaTime) — returns the new battery level. deltaTime is in seconds, so rates are battery-percent per second.
Config.batteryTick = function(currentBattery, deltaTime)
local vehicle = GetVehiclePedIsIn(PlayerPedId(), false)
if vehicle ~= 0 then
return math.min(100.0, currentBattery + (0.5 * deltaTime)) -- full charge in ~3 min
else
return math.max(0.0, currentBattery - (0.1 * deltaTime)) -- full drain in ~17 min
end
endCharging in any vehicle, driver or passenger, with no dock prop, is intentional. Add your own vehicle-type or prop condition here to restrict it. To switch battery drain off entirely, use Battery Drain in Admin Panel → Settings → General — that skips this hook and holds every battery at 100%.
Config.bgModeCheck() — polled about every 500 ms. Returns a mode string that selects the background sound other players hear behind your voice, or false for silence. The shipped version returns "helicopter" when piloting a helicopter, "k9" when a dog ped from Config.k9Models is in a passenger seat, and otherwise delegates to the configured siren provider. Mode strings map to WAV files in Admin Panel → Sounds → Background Sound Modes; see Background sounds.
Config.k9Models — ped model names that trigger "k9". Ships with seven GTA dog models; add your own custom K9 peds here.
Config.animations — the animation list players choose from in their settings. See Animations.
Zones and channels
Managed live in Admin Panel → Zones & Channels. Changes reach every connected client in real time.
A zone is a group of channels — usually a region or an agency. A channel is one frequency inside it.
Channel types
Conventional — one shared frequency. Everyone connected hears everyone else regardless of location. Use this for almost everything.
Trunked — units are assigned a sub-frequency automatically based on where they are, so crews in different parts of the map do not step on each other. Dispatchers reach every unit through the control frequency. Requires a frequency range (the sub-frequency pool) and a coverage radius in metres (the cell size).
Channel fields
| Field | Type | Default | Description |
|---|---|---|---|
| Name | string | — | Display name on the radio, e.g. DISP |
| Frequency | number | — | MHz. Must be unique across all zones |
| Type | select | conventional | conventional or trunked |
| Mode | select | analog | p25_digital runs this channel's audio through the P25 IMBE vocoder; analog does not |
| Dispatch | toggle | off | Monitor only — dispatchers can listen but not transmit (stores canTransmit: false) |
| Frequency range | two numbers | — | Trunked only. Start and end of the sub-frequency pool |
| Coverage | number | — | Trunked only. Cell radius in metres |
| Encryption | select | clear | secure marks the channel encrypted on the display |
| GPS colour override | blip ID | unset | Overrides the codeplug's GPS colour for players on this channel only |
Each channel has a generated channel ID shown in small text under its name (for example disp-lx1gi). That ID is what codeplugs reference — not the frequency, and not the name.
Hiding a zone from dispatch
Each zone has a Hide from dispatch toggle. A hidden zone is withheld from the dispatch panel entirely: its channels do not appear, and its traffic stays out of the transmission log and user counts. In-game it works exactly as before.
Use it for zones that exist purely for roleplay — civilian, criminal, or business radio — that would otherwise clutter a dispatcher's board.
This is display scope, not access control
Hiding a zone does not change which players can use its channels. To restrict player access, use codeplug channel access. A dispatcher connected to or scanning a channel when you hide its zone is removed from it on the next config refresh, and announcement groups skip hidden channels when broadcast.
Announcement groups
Also on the Zones & Channels page. A group is a named set of channel IDs. A dispatcher arms a group and their next PTT fans out to every channel in it simultaneously. The panel shows a GROUPS bar above the zone list whenever at least one group exists, and each group can get its own PTT key.
Two ship by default: * DISP and * C2C, each spanning all three starter zones.
Geo zones
Admin Panel → Geo Zones. A circular area that switches a player's radio to a target frequency when they walk in, and switches back when they leave.
| Field | Description |
|---|---|
| Position (X, Y, Z) | World coordinates of the centre |
| Radius | Metres |
| Vertical bounds | Optional min/max world Z, for stacked areas like parking garages. Min must be ≤ max |
| Target frequency | Must match a frequency on a configured channel, or the save is rejected |
Auto-switching is opt-in per player and per zone. A player must enable both the master Geo Auto-Switch toggle and the individual zone's checkbox in /tradio → Style. Zones a player's codeplug cannot reach are hidden from that list.
Use /_tradio_coords in-game to copy your current position straight into the X/Y/Z fields.
Codeplugs
A codeplug is a radio personality: which channels a player gets, which radio models they can pick, which features are on, and what their emergency button does. Managed in Admin Panel → Codeplugs; job assignment happens in sv_functions.lua.
Codeplug sections
| Section | What you set |
|---|---|
| Identity | ID (locked after creation — it is what JOB_CODEPLUG_MAP and ACE grants reference), name, and NAC. The NAC is cosmetic: it shows on the radio screen and dispatch panel and gates nothing |
| Allowed Models | Which radio models players may select. Selecting none leaves them with no radio |
| Default Layouts | The model shown first per context — on-foot focused, on-foot unfocused, ground vehicle, watercraft, air vehicle. Also per-spawncode and per-spawncode+livery overrides, which take priority |
| Channel Access | Channels this codeplug can connect and transmit on. Scan access is granted automatically for these. Zone access is derived — a zone is granted as soon as any of its channels is selected |
| Scan Access | Extra listen-only channels on top of the connect-access ones |
| Scan Lists | Named presets players load from /tradio → Scan Lists to populate their scan channels in one click. Each entry has a priority: 1 High, 2 Normal, 3 Low |
| Features | GPS Blips, Scan, Emergency Button, Man Down |
| Supervisor Access | Grants the in-game SGN sign-on button and trunked control-channel access, without granting server admin rights |
| Emergency Configuration | Which channel to switch to on emergency (or stay put), auto-transmit on/off, transmit duration in seconds, and panic tone repeat interval |
| GPS Visibility | Which other codeplugs' blips a player on this codeplug can see, plus this codeplug's own blip colour and its flash colour during panic |
Minimum viable codeplug: an ID, a name, at least one allowed model, and at least one channel. Everything else has a safe default.
Codeplug changes reach a player the next time their job loads or you call refreshNacId.
How access is resolved
Every radio open attempt and every refreshNacId call runs this chain. A player gets the union of codeplug access and ACE grants.
| Step | Gate | Denied when |
|---|---|---|
| 1 | Config.getPlayerAccess(serverId) | Returns false — no radio at all |
| 2 | Codeplug assignment via JOB_CODEPLUG_MAP, Config.getPlayerCodeplugId, or tRadio.codeplug.{id} ACE | No codeplug — no radio |
| 3 | Zone visibility — derived from selected channels, or tRadio.zone.{N} ACE | No channel in the zone and no ACE |
| 4 | Channel connect — codeplug channel access or tRadio.connect.{freq} ACE | Neither — channel hidden |
| 5 | Scan — codeplug scan access, connect access, or tRadio.scan.{freq} ACE | Neither — no scan |
| 6 | GPS visibility — codeplug GPS Visibility list or tRadio.gps.{freq} ACE | Neither — no blips |
| 7 | Supervisor controls (SGN, trunked CT/TK) | Requires tlib.admin ACE or the codeplug's Supervisor toggle |
ACE permissions
An alternative or supplement to codeplug assignment. tRadio.codeplug.{id} has the highest priority and overrides JOB_CODEPLUG_MAP.
| ACE | Grants |
|---|---|
tRadio.access | Bypass the inventory item requirement |
tRadio.codeplug.{id} | Assign codeplug {id} directly |
tRadio.connect.{freq} | Connect and transmit on a frequency |
tRadio.scan.{freq} | Scan (listen only) a frequency |
tRadio.gps.{freq} | See GPS blips on a frequency |
tRadio.zone.{N} | Access zone N by its 1-based position in the zone list |
tlib.admin | In-game SGN button, trunked CT/TK controls, and the 3D prop editor. Does not unlock the web admin panel |
Frequency keys drop the decimal point: 154.815 → 154815, 856.1125 → 8561125.
server.cfg
add_ace group.admin tRadio.access allow
add_ace identifier.steam:110000100000001 tRadio.codeplug.leo allow
add_ace group.ems tRadio.connect.154755 allow
add_ace group.ems tRadio.scan.8561125 allow
add_ace group.ems tRadio.gps.154755 allow
add_ace group.ems tRadio.zone.1 allow
add_ace group.admin tlib.admin allowtRadio.zone.N is positional
Zone ACEs key off a zone's position in the list, not a stable ID. Deleting a zone shifts every later-numbered grant. Review your ACE config after reordering or deleting zones — the admin panel warns you at the point of deletion.
Server settings
Admin Panel → Server Settings, four tabs. Every value below applies live to all connected clients unless marked restart required.
General tab
| Setting | Default | Description |
|---|---|---|
| Check for Updates | true | Check for new tRadio versions on start. Read live |
| Include Pre-release Updates | false | Also pull release candidates. For testers. Read live |
| 911 Calling | true | Experimental. Master switch for the whole 911 system. Disabling is enforced immediately server-side, but the /911 command itself only disappears for players who reconnect. Restart required |
| 911 Call Cooldown | 30 s | Experimental. Minimum wait between a player's 911 calls. 0 disables it; a 5-second server backstop still applies. Read live |
| Low Battery Chirp | true | Warning chirp under 50% battery, more frequent under 30% |
| Battery Drain | true | When off, every battery is held at 100% and Config.batteryTick is skipped |
| Log Level | 3 | 0 Error · 1 Warnings · 2 Minimal · 3 Normal · 4 Debug · 5 Verbose. Applies live |
| Radio Blips | true | Master switch for minimap blips of players on your channel |
| Blip Update Rate | 50 ms | Client-side render interval. Below 50 ms is not recommended |
| Radio Animations | true | Show the animation selector in player settings |
| Focus Layout Mode | false | Default for new players — a separate radio skin when the UI is open but unfocused. Needs a HandheldUnfocused default layout |
| Enable Callsigns | true | Let players set a personal callsign |
| Callsign Command | callsign | The chat command name, without the slash. Restart required |
| Man-Down by Default | false | New players have man-down monitoring on |
| Warning Delay | 45 s | How long a player must be down before the warning tone. Range 10–300 |
| Emergency Delay | 20 s | Time after the warning before it escalates to a full emergency broadcast. Range 5–120 |
| TTS by Default | true | New players hear spoken zone and channel announcements when switching |
Audio & Radio tab
Volume scales differ
The admin panel and dispatch panel use 0–100. The Lua export API uses 0.0–1.0. Divide by
100 when passing a config value to an export: voiceVolume = 65 → setVolume(0.65).
Audio Defaults — applied when a player first connects; they can override them.
| Setting | Default | Description |
|---|---|---|
| Voice Volume | 65 | Default radio voice volume (0–100) |
| SFX Volume | 35 | Default tone and effect volume (0–100) |
| 3D Audio Volume | 50 | Default 3D spatial volume (0–100) |
| Volume Step | 5 | Change per press of a volume keybind. Range 1–25 |
PTT & Transmission
| Setting | Default | Description |
|---|---|---|
| PTT Release Delay | 350 ms | How long the radio stays open after releasing PTT, so speech is not clipped. Range 0–2000 |
| PTT Triggers Proximity | true | With no radio equipped, PTT activates proximity voice instead of doing nothing. Requires MumbleVoice proximity audio |
| Transmission Tones | true | Play TX_START / TX_END tones when someone begins or ends transmitting |
| Analog Tones | true | Use analog-style squelch sounds (PTT / PTT_END, heard by the transmitter) instead of the digital receiver tones |
| Panic Alert Timeout | 60 s | How long a panic stays active before auto-clearing. 0 = manual clearance only |
| Panic Alert Cooldown | 15 s | Minimum wait before the same player can panic again on that channel |
Half-Duplex (Bonking) — what happens when someone keys up while another unit is talking.
| Setting | Default | Description |
|---|---|---|
| Block Transmission | true | Deny the PTT attempt. When off, the transmission goes through but a collision tone plays |
| Play Bonk Tone | true | Play a tone to the blocked user |
| Bonk Tone to All | true | Everyone on the channel hears it, not just the sender |
| Double-Tap Override | true | Two quick PTT presses force a blocked transmission through |
| Double-Tap Window | 1500 ms | How close the two presses must be. Range 100–5000 |
3D Positional Audio — everything below the master switch is hidden until it is on.
| Setting | Default | Description |
|---|---|---|
| Enable 3D Audio | false | Master switch — allows players to turn 3D audio on |
| 3D Audio On by Default | false | New players start with it enabled (their Earbuds toggle off) |
| Vehicle 3D Audio | true | A vehicle the player has been in keeps rebroadcasting their radio from itself |
| Vehicle 3D Distance | 3 m | How far the player must walk from the vehicle before it takes over as the source |
| Player Speaker Range | 35 m | How far a handheld radio carries |
| Vehicle Speaker Range | 60 m | How far a vehicle-sourced radio carries |
Signal Coverage
| Setting | Default | Description |
|---|---|---|
| Always Full Signal | false | Force full bars and no degradation, without deleting your tower configuration |
| Signal Degradation | false | Reduce audio quality with distance from the nearest tower |
| Falloff Rate | 24 | How fast quality drops with distance. 0 gradual, 100 steep |
| Degradation Intensity | 50 | How severe the degradation is at low signal |
Degradation does nothing until towers exist — see Signal towers.
Dispatch Scanning
| Setting | Default | Description |
|---|---|---|
| Simultaneous Scan Audio | false | Off: a transmission on the dispatcher's main channel mutes scanned channels until it ends. On: every scanned channel plays independently, overlapping |
Radio Audio Effects — see Radio FX.
Controls tab
Default keybinds registered through FiveM's RegisterKeyMapping. Players can rebind any of them in FiveM → Settings → Key Bindings → FiveM. Click a field to capture a key, right-click to clear it (registering the action unbound).
| Action | Default |
|---|---|
| Push-to-Talk | B |
| Toggle Radio | F6 |
| Voice Volume Up / Down | = / - |
| SFX Volume Up / Down | ] / [ |
| Close Radio, Power, Channel Up/Down, Zone Up/Down, Menu Up/Down/Left/Right/Home, Menu Buttons 1–3, Emergency | unbound |
| Style Up / Down | unbound — hidden unless Radio Animations is on |
| 3D Volume Up / Down | unbound — hidden unless 3D Audio is on |
Advanced tab
Earpiece / Items — GTA outfit slots that switch a player's earbuds on automatically. With earbuds active a player emits no 3D audio, so nearby players cannot hear their radio.
| Field | Description |
|---|---|
| Type | prop for accessories, component for clothing |
| Slot | The GTA slot — props include Hat/Helmet (0), Glasses (1), Ear/Earpiece (2), Watch (6), Bracelet (7) |
| Drawable ID | The 0-based clothing variant index. Find it with a ped editor |
Clothing is checked every 7 seconds. A manual earbud toggle is respected until the player's clothing changes again.
LB Phone / LB Radio Integration — see LB Phone and LB Radio. Experimental.
ImperialCAD Integration — see ImperialCAD.
Sounds
Admin Panel → Sounds. All changes broadcast instantly.
Tone catalog
Every tone is a named slot mapped to a WAV file. Map them under Global Tone Mappings, and upload new WAVs from Marketplace → Sounds.
| Tone | Plays when |
|---|---|
PTT / PTT_TRUNKED / PTT_PATCHED | You key up — conventional / trunked / patched |
PTT_END / PTT_END_TRUNKED / PTT_END_PATCHED | You release |
TX_START / TX_START_TRUNKED / TX_START_PATCHED | Someone else keys up |
TX_END / TX_END_TRUNKED / TX_END_PATCHED | Someone else releases |
BEEP | Generic alert beep |
BONK | Transmission collision |
CHIRP | Short acknowledgement, also the low-battery chirp |
ECHO | Message received |
PANIC | Panic button activation |
ALERT_A / ALERT_B / ALERT_C | Default alert-tone slots |
ALERT_SIGNAL3 | Signal 3 alert tone |
PRIORITY / PRIORITY_REPEAT | Priority alert activation and each repeat |
RING_OUT | Heard by a 911 caller while their call rings |
RING_IN | Heard by dispatchers when a 911 call arrives |
Per-model overrides replace a single tone for one radio model only, taking priority over the global mapping. You can also drop WAVs directly at layouts/{MODEL}/sounds/{TONE_NAME}.wav.
Background sounds
These fixed-filename WAVs in layouts/sounds/ play during a received transmission and cannot be renamed:
| File | Purpose |
|---|---|
transStart.wav | Click-in at the start of a received transmission |
transMid.wav | Looping background noise during it |
transEnd.wav / transEnd1.wav / transEnd2.wav | Click-out — one of the three is picked at random |
bgSiren.wav | Siren loop |
bgHeli.wav | Helicopter rotors |
bgDog.wav | K9 |
bgShot.wav | Gunshot fallback |
Background Sound Modes maps the strings Config.bgModeCheck returns to WAV files. Three ship mapped: siren → bgSiren.wav, helicopter → bgHeli.wav, k9 → bgDog.wav. Return any other string from bgModeCheck, add the mapping here, and upload the WAV — unmapped modes simply play nothing.
For example, to play a different WAV per LVC siren tone, return distinct modes from bgModeCheck:
if tone == 2 then return "sirenWail" end
if tone == 3 then return "sirenYelp" end
if tone > 0 then return "siren" endthen map sirenWail and sirenYelp here.
Siren detection
Which ELS resource bgModeCheck reads to decide a siren is on.
| Provider | Value |
|---|---|
| Auto — try each installed provider in order | auto (default) |
| tELS only | tels |
| Luxart Vehicle Control only | lvc |
| LVC Fleet only | lvc_fleet |
| ELS-FiveM only | els_fivem |
| JLS only | jls |
| Advanced Lighting System only | als |
| Native GTA siren only | native |
Skip detection — implement bgModeCheck yourself | custom |
Auto order is tELS → LVC / LVC Fleet → ELS-FiveM → JLS → ALS → native. Each provider returns "not my vehicle" when it does not recognise the car, so mixed fleets work. Resource-name overrides for tELS, LVC, and JLS are on the same page and only needed if you renamed one; LVC Fleet, ALS, and ELS-FiveM use events and need no name.
To force an immediate re-poll after a custom siren state change:
exports['tRadio']:NotifySirenChanged()Radio FX
The DSP chain applied to all incoming radio voice: mic → input gain → highpass → lowpass → compressor → limiter → saturation → mid EQ → output.
| Setting | Default | Range |
|---|---|---|
| Radio FX | true | Master switch. Everything below is hidden when off |
| Distortion Mode | classic | classic hard-clip, tube smooth saturation |
| Highpass Cutoff | 250 Hz | 80–800 |
| Lowpass Cutoff | 3500 Hz | 1200–8000 |
| Compression | 60 | 0–100 |
| Distortion | 30 | 0–100 |
| Mid Boost | 2 dB | −12 to 12 |
| Input Gain | 1.65 | 0.5–3.0 |
| Problem | Fix |
|---|---|
| Too muffled, telephone-like | Raise Lowpass Cutoff to 4000–5000 |
| Too tinny or harsh | Lower Highpass Cutoff to 150–200 |
| Volume pumping | Lower Compression |
| Crackling or clipped | Lower Input Gain to 0.8–1.2, or lower Distortion |
P25 is separate and per-channel. There is no global P25 switch. Set a channel's Mode to P25 Digital and every listener on it — in-game, dispatch, and 3D bystanders — hears real IMBE vocoder audio. The analog FX chain applies on top. The first transmission of a session has a brief WASM warm-up.
Alerts
Admin Panel → Alerts. An alert puts a coloured banner on every radio connected to the channel. The first alert in the list is what the in-game SGN button fires.
An alert has three phases, each with an optional tone: activate (once), repeat (persistent alerts only, every repeatInterval), and deactivate (once, on clear).
| Field | Default | Description |
|---|---|---|
| Name | — | Display text on the radio |
| Color | — | Hex colour of the banner |
| Persistent | false | Stays active until manually cleared |
| Tone | — | Single tone used for all phases |
| Multi-tone | off | Switches to separate activate / repeat / deactivate tones |
| Repeat every | random 5–10 s | Gap between repeats. Minimum 1000 ms |
| Show banner on repeat | true | Flash the banner on each repeat |
| Deactivate label | RESUME | Banner text shown on clearance |
| Deactivate color | #126300 | Colour of the clearance banner |
| Tone Only | false | Play the tone with no visual banner |
| Channel filter | all channels | Restrict this alert to specific channels |
Four ship by default: SIGNAL 100, SIGNAL 3, Ping, and Bonk.
Speakers
Admin Panel → Speakers. World-placed tone-board speakers that dispatchers can broadcast a tone to from the panel — station alerting, tone-outs, PA stingers.
| Field | Default | Description |
|---|---|---|
| Position (X, Y, Z) | — | World coordinates |
| Heading | 0 | Prop rotation in degrees. Only affects the visible prop |
| Radius | 60 m | Audible reach. Controls both who receives the tone and how far it carries. Applies from the next tone |
| Prop Model | none | Optional. Leave blank to place no prop — the speaker still plays from its coordinates |
| Group | none | Speakers sharing a group name are one selectable target on the dispatch panel |
Grab coordinates in-game with /_tradio_coords. Dispatchers use them from the Speakers button in the dispatch panel; see Usage.
Signal towers
Admin Panel → Towers. Physical towers that provide coverage. Signal strength falls off with distance from the nearest one, and with Signal Degradation on, audio quality degrades with it.
| Field | Description |
|---|---|
| Name | Label in the panel |
| Position (X, Y, Z) | World coordinates |
| Prop model | Optional. Blank places no prop — the tower still provides coverage |
Signal strength is measured from the nearest tower, so coverage is a union of each tower's reach rather than a sum. Place them where you want usable comms, and leave gaps where you want players to lose signal.
Towers configured before this page existed are migrated automatically on first start, with no action needed.
Integrations
LB Phone and LB Radio
Fully managed in Admin Panel → Settings → Advanced → LB Phone / LB Radio Integration. Nothing to configure in config.lua.
Experimental
Both integrations are experimental, and the phone dial-in is an entry point into the 911 system, which is experimental in its own right. Leave them off if you would rather not run them on a live server yet.
| Setting | Default | Description |
|---|---|---|
| LB Phone 911 Dial-In | Off | Players dial 911 from the in-game phone and receive call status as phone notifications. Requires lb-phone |
| LB Radio Frequency Mirror | Off | Mirrors the player's frequency, connection status, and user count into LB Phone's Radio app. Read-only and cosmetic. Requires lb-radio |
The two are independent — you can run either one alone, or neither.
The phone integration replaces /911 — it does not add to it
While LB Phone 911 Dial-In is on, the /911 chat command is not registered at all. Both
routes reach the same queue, so nothing else changes. If lb-phone is not actually installed and
started, players have no way to reach 911 — tRadio logs a warning at startup when it detects
exactly this. Turn the setting off to get the chat command back.
The phone dial-in carries a restart badge: each player is given exactly one 911 entry point when they connect, and FiveM cannot un-register a chat command or an lb-phone custom number mid-session. Already-connected players keep whichever entry point they were given until they reconnect or the resource restarts. The mirror has no such constraint and applies live.
The 911 system as a whole is switched on and off in Admin Panel → Settings → General → 911 Calling.
ImperialCAD
Fully managed in Admin Panel → Settings → Advanced. Nothing to configure in config.lua, and both toggles apply live.
| Setting | Default | Description |
|---|---|---|
| Enable ImperialCAD Integration | false | Mirror every queued 911 call into ImperialCAD as a parallel call record |
| Sync Panic to ImperialCAD | true | Also mirror radio panic activations into ImperialCAD's Panic() / ClearPanic() |
Requires the ImperialCAD resource to be started under exactly that name, with its own imperial_community_id and imperialAPI convars set per ImperialCAD's own docs. tRadio only calls its exports. If the integration is on but the resource is not running, tRadio logs the reason at startup and on every call rather than failing silently.
Call ownership. A mirrored call is deleted from ImperialCAD only if it dies unanswered — queue timeout, or the caller hangs up while still ringing. Once a tRadio dispatcher accepts it, tRadio never touches ImperialCAD's copy again, on the assumption a CAD dispatcher now owns it.
911 hooks
Two optional hooks in config/sv_functions.lua. Both have sensible defaults and neither needs editing.
-- The caller ID a dispatcher sees. A real 911 dispatcher only ever gets a number,
-- so this — not getPlayerName — is what the call card shows.
-- Default: the caller's real LB Phone number when available, otherwise a stable
-- 555-XXXX placeholder derived from their license, so the same player always gets
-- the same number across sessions.
Config.getCall911CallerNumber = function(serverId)
return "555-0142"
end
-- Fires when a call is placed while no dispatch panel is connected. The call still
-- queues and appears the moment a dispatcher logs in — this is an immediate,
-- best-effort notice so it is not silently missed.
-- Default: chat-broadcast to every player with radio access.
-- `call` fields: callerName, callerUnitId, callerNumber, message, coords, frequency, createdAt.
Config.call911NoDispatcherFallback = function(call)
-- Discord webhook, NPC dispatch, or your own alerting here
endHook in from your own resource instead
radio:911:noDispatcherOnline(call) fires as a normal server event, so you can AddEventHandler
it from your own resource rather than editing sv_functions.lua — which keeps your customisations
across a tRadio update. radio:911:answered and radio:911:ended work the same way.
Multi-server federation
Run several game servers against one shared voice server and dispatch panel, so dispatchers see every unit in one place.
- Host (Server A) runs the voice backend. Leave
isSatelliteServer = falseand keep its ownserverAddressandauthToken. - Satellites (Server B and up) run no backend of their own. They point at the host and federate player, panic, tone, and alert state to it over HTTP.
config/config.lua on each satellite:
Config = {
serverAddress = "http://203.0.113.10:7777", -- Server A's public URL, port included
isSatelliteServer = true,
authToken = "<same value as Server A>", -- must match Server A exactly
}The authToken must match exactly
Federation requests are HMAC-signed with authToken. A mismatch fails validation and the
satellite's players never appear on the host's dispatch panel.
A satellite binds no HTTP or Socket.IO server, so serverAddress must be set — an empty value is rejected with an explicit error at startup. Satellites also pull zone, channel, and codeplug config from the host roughly every 30 seconds, so an admin panel change on the host reaches them without a restart.
911 calls require the host's voice server, so a satellite's callers connect through Server A like everyone else.
