Configuration

Complete configuration reference for Tommy's Radio — config.lua, admin panel, zones, channels, alerts, and tones.

config.luaAdmin Panel

Configuration Overview

Tommy's Radio uses two configuration sources:

SourceContainsEdited via
config.luaNetwork, keybinds, zones/channels, alerts, callbacksText editor (requires restart)
data.jsonAudio, radio FX, bonking, callsigns, 3D audio, GPS rate, default layoutsIn-game admin panel (live)

Settings in data.json are managed through the admin panel and broadcast to all clients immediately — no restart needed.


config.lua Reference

Network & Authentication

config.lua
Config = {
    serverAddress = "",           -- Connection URL sent to clients. Leave empty for auto-detect.
                                  -- Set to "http://IP:PORT/" if using a proxy or specific IP.
    serverPort    = 7777,         -- Port for voice server and dispatch panel
    authToken     = "changeme",   -- Secret key for API authentication. Change this.
    dispatchNacId = "141",        -- Dispatch panel login password
}

General

config.lua
Config = {
    checkForUpdates = true,       -- Check for updates on resource start
    healthCheck     = true,       -- Run port reachability check on start
    logLevel        = 3,          -- 0=Error, 1=Warn, 2=Minimal, 3=Normal, 4=Debug, 5=Verbose
    useDiscordAuth  = false,      -- Use Discord OAuth for dispatch panel (see Dispatch Panel page)
}

Keybinds

config.lua
Config.controls = {
    talkRadioKey       = "B",     -- Push-to-talk
    toggleRadioKey     = "F6",    -- Open/close radio
    closeRadioKey      = "",      -- Close radio only (separate from toggle)
    powerBtnKey        = "",      -- Power on/off
    channelUpKey       = "",      -- Next channel
    channelDownKey     = "",      -- Previous channel
    zoneUpKey          = "",      -- Next zone
    zoneDownKey        = "",      -- Previous zone
    menuUpKey          = "",      -- Navigate menu up
    menuDownKey        = "",      -- Navigate menu down
    menuRightKey       = "",      -- Navigate menu right
    menuLeftKey        = "",      -- Navigate menu left
    menuHomeKey        = "",      -- Return to menu home
    menuBtn1Key        = "",      -- Menu button 1 (context-dependent)
    menuBtn2Key        = "",      -- Menu button 2 (context-dependent)
    menuBtn3Key        = "",      -- Menu button 3 (context-dependent)
    emergencyBtnKey    = "",      -- Panic / emergency button
    styleUpKey         = "",      -- Next radio model
    styleDownKey       = "",      -- Previous radio model
    voiceVolumeUpKey   = "",      -- Voice volume up
    voiceVolumeDownKey = "",      -- Voice volume down
    sfxVolumeUpKey     = "",      -- SFX volume up
    sfxVolumeDownKey   = "",      -- SFX volume down
    volume3DUpKey      = "",      -- 3D audio volume up
    volume3DDownKey    = "",      -- 3D audio volume down
    -- Leave empty ("") to disable a keybind
}

Signal Towers

Positions for the signal-strength icon and optional audio degradation.

config.lua
Config.signalTowerCoordinates = {
    { x = 1860.0,  y = 3677.0,  z = 33.0 },
    { x = 449.0,   y = -992.0,  z = 30.0 },
    -- Add, remove, or move coordinates to match your map
}

The radio shows more signal bars when closer to a tower. When signal degradation is enabled (via admin panel), distance also affects audio quality — further from towers means more garbled audio with random dropouts.

Battery

The batteryTick callback controls battery drain and charge rates.

config.lua
Config.batteryTick = function(currentBattery, deltaTime)
    local vehicle = GetVehiclePedIsIn(PlayerPedId(), false)
    if vehicle ~= 0 then
        return math.min(100.0, currentBattery + (0.5 * deltaTime))  -- Charge in vehicle
    else
        return math.max(0.0, currentBattery - (0.1 * deltaTime))    -- Drain on foot
    end
end

Client Callbacks

talkCheck

Runs before each PTT transmission. Return false to block.

config.lua
Config.talkCheck = function()
    if IsPlayerDead(PlayerId()) then return false end
    if IsPedSwimming(PlayerPedId()) then return false end
    -- Add custom conditions: handcuffs, tackle, etc.
    return true
end

bgSirenCheck

Determines if siren background SFX should play when a player transmits. Other players on the channel hear the siren looping behind the voice audio.

Return values:

ReturnBehavior
false / nilNo siren audio
trueDefault siren (bgSiren.wav)
"filename.wav"Custom audio file from client/radios/default/sounds/
config.lua
Config.bgSirenCheck = function(lvcSirenState)
    local vehicle = GetVehiclePedIsIn(PlayerPedId(), false)
    if vehicle == 0 then return false end
    return lvcSirenState and lvcSirenState > 0
end
Custom Siren Audio (LVC Example)

By returning a filename instead of true, you can play the actual siren tone matching what's active in-game. This requires siren WAV files placed in client/radios/default/sounds/ named by siren ID (e.g., 1.wav, 2.wav, 21.wav).

To use this with LVC, add @lvc/UTIL/cl_lvc.lua to your fxmanifest.lua client scripts, then modify bgSirenCheck:

config.lua
Config.bgSirenCheck = function(lvcSirenState)
    local playerPed = PlayerPedId()
    if not playerPed or playerPed == 0 then return false end

    local vehicle = GetVehiclePedIsIn(playerPed, false)
    if not vehicle or vehicle == 0 then return false end

    -- LVC: return the siren ID as a filename so the correct tone plays over radio
    if state_lxsiren and state_lxsiren[vehicle] then
        local sirenId = state_lxsiren[vehicle]
        if sirenId and sirenId > 0 then
            return tostring(sirenId) .. ".wav"
        end
    end

    -- Fallback: default siren if native siren is on
    if IsVehicleSirenOn(vehicle) then return true end
    return false
end

With this setup, when a player has their wail siren active (e.g., siren ID 1), other players hear 1.wav over the radio instead of the generic siren loop. Switching to yelp (e.g., ID 21) plays 21.wav on the next transmission.

The siren audio updates between transmissions. If a player switches siren tones mid-transmission, the new tone takes effect the next time they key up.

Server Callbacks

These three functions run server-side and control who can use the radio and what they can access.

The string returned by getUserNacId is the player's NAC ID — the radio compares it against:

  • Zone nacIds — player can see the zone only if their NAC ID is in the list
  • Channel allowedNacs — player can connect and transmit only if their NAC ID is in the list
  • Channel scanAllowedNacs — player can scan (listen only) if their NAC ID is in the list
config.lua
-- Gate: who can use the radio at all (return false to block)
Config.radioAccessCheck = function(playerId)
    return true
end

-- Identity: NAC ID assigned to this player (matched against zones/channels)
Config.getUserNacId = function(serverId)
    return "141"
end

-- Display name shown on the radio and in the dispatch panel
Config.getPlayerName = function(serverId)
    return GetPlayerName(serverId)
end

For framework-specific examples (QBCore, ESX, Qbox job-based NAC IDs), see Framework Integration. When a player's job changes, call exports['tRadio']:refreshNacId(serverId) to update their access immediately.

ACE Permissions

As an alternative to the getUserNacId callback, you can assign permissions directly through FiveM ACE entries in server.cfg. ACE permissions are additive — they work alongside NAC IDs, so existing configs don't need to change.

NAC ID via ACE

Assign a NAC ID to a player or group without writing Lua code. If getUserNacId returns nil, the server checks for a matching tRadio.nac.* ACE:

server.cfg
# Give police group NAC ID "141"
add_ace group.police tRadio.nac.141 allow
add_principal identifier.steam:STEAM_ID group.police

# Give EMS NAC ID "110"
add_ace group.ems tRadio.nac.110 allow

If getUserNacId returns a value, it takes priority over ACE NAC IDs. To use ACEs exclusively, have getUserNacId return nil.

Per-Frequency ACE Permissions

Grant access to specific frequencies without a NAC ID. The frequency is encoded by removing the decimal point (e.g., 154.815154815).

ACE PermissionGrants
tRadio.connect.{freq}Transmit on a frequency
tRadio.scan.{freq}Scan (listen only) on a frequency
tRadio.gps.{freq}See GPS blips for a frequency
tRadio.zone.{N}Access zone N (1-based index from Config.zones)
server.cfg
# EMS can transmit on 154.755 and 154.815
add_ace group.ems tRadio.connect.154755 allow
add_ace group.ems tRadio.connect.154815 allow

# EMS can scan 856.1125
add_ace group.ems tRadio.scan.8561125 allow

# EMS can see GPS blips on 154.755
add_ace group.ems tRadio.gps.154755 allow

# EMS can access zone 1 (Statewide)
add_ace group.ems tRadio.zone.1 allow

Admin Radio Features via ACE

Players with the tlib.admin ACE permission (same permission used for the in-game admin panel) gain access to additional radio controls — the SGN alert button and trunked CT/TK toggle.

server.cfg
add_ace group.admin tlib.admin allow

Combining NAC IDs and ACE Permissions

A player's effective access is the union of their NAC ID permissions and ACE permissions. For example, a player with NAC ID "110" and tRadio.connect.154815 can access everything NAC "110" grants plus transmit on frequency 154.815.

When permissions change (e.g., a job update triggers refreshNacId), both NAC ID and ACE permissions are re-resolved and sent to the client. If a player loses access to their current channel, they are automatically disconnected.


Admin Panel (data.json)

Settings managed through the in-game admin panel. Access requires the tlib.admin ACE permission.

Grant it in server.cfg:

server.cfg
add_ace identifier.steam:STEAM_ID tlib.admin allow

Or grant to a group:

server.cfg
add_ace group.admin tlib.admin allow
add_principal identifier.steam:STEAM_ID group.admin

Open via the /tradio command > Admin Settings.

Audio Settings

SettingDefaultDescription
voiceVolume65Default voice volume (0-100)
sfxVolume35Default SFX/tone volume (0-100)
volumeStep5Volume adjustment increment
pttReleaseDelay350Milliseconds to keep transmitting after releasing PTT
pttTriggersProximitytruePTT also triggers proximity voice

Radio FX

SettingDefaultDescription
radioFx.fxEnabledtrueEnable analog audio processing chain
radioFx.p25EnabledfalseEnable P25 IMBE vocoder
radioFx.distortion40Distortion amount
radioFx.inputGain1.65Input gain multiplier
radioFx.compression100Compression amount
radioFx.midBoost3Mid-frequency boost (dB)
radioFx.highpassFrequency300Highpass filter cutoff (Hz)
radioFx.lowpassFrequency3000Lowpass filter cutoff (Hz)

P25 and analog FX can be used independently or together. Using both stacks the analog chain on top of the vocoder output.

Transmission Effects

SettingDefaultDescription
playTransmissionEffectstruePlay background SFX (sirens, gunshots, helicopters)
analogTransmissionEffectstrueApply analog processing to transmission effects

Bonking

Controls what happens when a player tries to transmit while someone else is already talking.

SettingDefaultDescription
bonking.playBonkTonetruePlay a tone when transmission is blocked
bonking.bonkToneGlobaltrueAll channel users hear the bonk tone
bonking.blockTransmissiontrueBlock overlapping transmissions
bonking.doubleTapOverridetrueDouble-tap PTT to override and transmit anyway
bonking.doubleTapWindow1500Time window (ms) for double-tap detection

3D Audio

SettingDefaultDescription
enable3DAudiotrueEnable 3D spatial audio system
default3DAudiotrue3D audio on by default for new players
default3DVolume50Default 3D audio volume (0-100)
vehicleRadio3DAudioEnabledtrueRadio plays near vehicle when player walks away
vehicle3DActivationDistance1.0Distance from vehicle before vehicle radio activates

Callsign System

SettingDefaultDescription
useCallsignSystemtrueEnable the built-in callsign system
callsignCommand"callsign"Chat command to set callsign (e.g., /callsign 2L-319)

Earbud Clothing Detection

Automatically enable earbuds mode when a player wears specific clothing items (hats, earpieces, accessories). Configured through the admin panel — no restart required.

Each entry in the earbudItems list defines a clothing slot and drawable ID to match:

FieldTypeDescription
slotIdnumberGTA V component/prop slot ID
slotTypestring"prop" for accessories/hats, "component" for clothing
drawableIdnumberThe drawable index to match (use the 3D Prop Editor or GTA ped tools to find IDs)

Behaviour:

  • Checked every 7 seconds via a polling thread
  • Only triggers on state change (wearing → not wearing or vice versa)
  • If the player manually toggles earbuds, the auto-detection respects that override until the clothing state changes again

GPS & Signal

SettingDefaultDescription
gpsBlipUpdateRate50GPS blip update interval (ms). Lower = smoother, higher CPU
signalDegradationEnabledfalseAudio quality degrades with distance from signal towers
signalDegradationIntensity50How aggressive signal degradation is (0-100)
signalFalloffRate50How quickly signal drops with distance

Emergency

SettingDefaultDescription
panicTimeout60000Milliseconds before panic auto-clears

Default Layouts

Default radio model per vehicle type. Configurable via admin panel or /tradio > Set as Default Layout for Vehicle.

Vehicle TypeDefault Model
HandheldATX-8000
VehicleAFX-1500
BoatAFX-1500G
AirTXDF-9100

Radio models are auto-discovered from client/radios/ subfolders.


Zones & Channels

Zones group channels together. Players see zones based on their NAC ID matching the zone's nacIds list.

Zone Structure

config.lua
Config.zones = {
    [1] = {
        name = "Statewide",
        nacIds = { "141", "200" },  -- NAC IDs that can see this zone
        Channels = {
            [1] = {
                name = "DISPATCH",
                type = "conventional",
                frequency = 154.755,
                allowedNacs = { "141" },
                scanAllowedNacs = { "200" },
                gps = {
                    color = 54,
                    visibleToNacs = { "141" },
                },
            },
        },
    },
}

Channel Types

Conventional

Single shared frequency — all connected players hear each other regardless of location.

Conventional Channel
{
    name = "DISPATCH",
    type = "conventional",
    frequency = 154.755,
    allowedNacs = { "141" },
    scanAllowedNacs = { "200" },
    gps = { color = 54, visibleToNacs = { "141" } },
}

Trunked

Location-based frequency assignment. Units at different locations get separate sub-frequencies automatically. Dispatchers can still broadcast to all units on the control frequency.

Trunked Channel
{
    name = "CAR-TO-CAR",
    type = "trunked",
    frequency = 856.1125,                  -- Control frequency
    frequencyRange = { 856.000, 859.000 }, -- Available range
    coverage = 500,                        -- Radius in meters
    allowedNacs = { "141" },
    gps = { color = 25, visibleToNacs = { "141" } },
}

Channel Field Reference

FieldTypeDescription
namestringDisplay name on the radio
typestring"conventional" or "trunked"
frequencynumberFrequency in MHz (must be unique across all zones)
frequencyRangetableTrunked only — { min, max } range
coveragenumberTrunked only — radius in meters
allowedNacstableNAC IDs that can connect and scan
scanAllowedNacstableNAC IDs that can scan only (not connect)
gps.colornumberBlip color ID
gps.visibleToNacstableNAC IDs that can see this channel's GPS blips

Alerts

Alerts are defined in Config.alerts. The first entry ([1]) is the default triggered by the in-game SGN button. Players whose NAC ID matches dispatchNacId can trigger it from the radio; all alerts can also be triggered from the dispatch panel or API.

Tone Styles

Simple — one tone for all phases:

config.lua
{
    name = "SIGNAL 3",
    color = "#0049d1",
    isPersistent = true,
    tone = "ALERT_A",
}

Per-phase — independent tones for activate, repeat, and deactivate:

config.lua
{
    name = "SIGNAL 100",
    color = "#d19d00",
    isPersistent = true,
    tones = {
        activate   = "PRIORITY",
        ["repeat"] = "PRIORITY_REPEAT",
        deactivate = "ALERT_C",
    },
}

Any phase can be omitted — it falls back to the tone field, or plays nothing if absent.

Alert Field Reference

FieldTypeDefaultDescription
namestringDisplay text on the radio
colorstringHex colour of the alert banner
isPersistentboolfalseStays active until manually cleared
tonestringFallback tone for all phases
tones.activatestringTone played once on activation
tones["repeat"]stringTone played on each repeat loop
tones.deactivatestringTone played once on clearance
repeatIntervalnumber (ms)random 5-10sGap between repeat fires
repeatShowBannerbooltrueFlash the banner on each repeat
deactivateLabelstring"RESUME"Banner text shown on clearance
deactivateColorstring"#126300"Colour of the clearance banner
toneOnlyboolfalsePlay tone only, no visual banner

Example

config.lua
Config.alerts = {
    [1] = {
        name = "SIGNAL 100",
        color = "#a38718",
        isPersistent = true,
        tones = {
            activate   = "PRIORITY",
            ["repeat"] = "PRIORITY_REPEAT",
            deactivate = "ALERT_C",
        },
        repeatInterval   = 15000,
        repeatShowBanner = false,
        deactivateLabel  = "RESUME",
        deactivateColor  = "#1a8a38",
    },
    [2] = {
        name = "SIGNAL 3",
        color = "#1852a3",
        isPersistent = true,
        tone = "ALERT_A",
    },
    [3] = {
        name = "Ping",
        color = "#1852a3",
        tone = "ALERT_B",
    },
    [4] = {
        name = "Boop",
        color = "#1c4ba3",
        toneOnly = true,
        tone = "BONK",
    },
}

Custom Tones

Tones are defined in client/radios/default/tones.json. Each key is the tone name (case-insensitive).

Oscillator Tones

Synthesised on the fly by the Web Audio API:

client/radios/default/tones.json
{
  "BEEP": [{ "freq": 910, "duration": 250 }],
  "ALERT_A": [
    { "freq": 600, "duration": 150, "delay": 200 },
    { "freq": 600, "duration": 150 }
  ]
}
PropertyDescription
freqFrequency in Hz (20-20000)
durationLength in milliseconds
delayOptional pause before this step (ms)

WAV File Tones

Set a tone's value to an empty array [] to play a .wav file instead. The file name must match the tone key (lowercase).

client/radios/default/tones.json
{
  "ECHO": [],
  "SIREN": []
}

"ECHO": [] looks for echo.wav in client/radios/default/sounds/. Supported formats: .wav, .mp3, .ogg (.wav recommended for lowest latency).

WAV tones work everywhere oscillator tones do: in-game radio, 3D spatial audio, and the dispatch panel.

Per-Model Overrides

Place a tones.json in client/radios/YOUR-MODEL/ to override individual tones for that model. Keys present take precedence; others fall back to client/radios/default/tones.json.

client/radios/
+-- default/
|   +-- tones.json
|   +-- sounds/echo.wav
+-- AFX-1500/
    +-- tones.json          (overrides specific tones)
    +-- sounds/echo.wav     (model-specific WAV)

On this page

Need help?

Ask on Discord