Setup & Configuration
Complete installation and configuration guide for Tommy's Radio system.
Quick Start
Prerequisites
- FiveM Server
- Firewall configuration access
- Port available for voice server (default: 7777)
Installation Steps
1. Extract Files
- Download the radio resource files
- Extract to your server's
resources
folder - Ensure the folder is named
radio
Important
Keep the resource name as "radio" for proper export functionality.
2. Basic Configuration
Edit config.lua
with your basic settings:
Config = {
-- Network Configuration
serverPort = 7777, -- Choose an unused port
authToken = "your-secure-token-123", -- Change this!
dispatchNacId = "141", -- Dispatch access code (can trigger first alert)
-- Default Controls
controls = {
talkRadioKey = "B",
toggleRadioKey = "F6",
},
-- Audio Settings
voiceVolume = 60, -- 0-100
sfxVolume = 20, -- 0-100
}
3. Firewall Setup
Open the configured port on your server:
sudo ufw allow 7777
netsh advfirewall firewall add rule name="Radio Voice Server" dir=in action=allow protocol=TCP localport=7777
Environment Specific
Firewall and network configuration varies significantly between hosting environments, operating systems, and server providers. We do not provide specific support for these setups as they are outside the resource scope. Many online tutorials and resources are available for your specific environment.
4. Start Resource
Add to server.cfg
:
start radio
5. Setup Dispatch Panel
Option 1: Desktop App (Recommended)
Due to browser limitations — including the requirement for a secure (HTTPS) domain to enable microphone access and the lack of support for global hotkeys — we offer a native Windows desktop application which provides full functionality.
Download available on the main page dispatch panel section.
Benefits:
- No browser limitations
- Global hotkeys support
- Full microphone access
- Better performance
Option 2: Web Browser
Access via: http://your-server-ip:your-port
Browser Limitations
Most browsers block microphone access on HTTP. You'll need HTTPS or use browser flags for local development.
For Development/Testing:
- Use the
unsafely-treat-insecure-origin-as-secure
Chrome flag - Set flag for your dispatch panel URL
For Production:
- Use HTTPS with SSL certificate
- Use reverse proxy with domain
SSL/HTTPS Setup
For production environments, you'll want to setup HTTPS to enable full microphone functionality in browsers. This can be done through SSL certificates for your IP address, or by using a reverse proxy (like Nginx/Apache) with a domain and SSL certificate. This setup varies significantly by hosting provider and environment - there are many online tutorials available for your specific setup.
Framework Integration
NAC ID System
The permission system uses NAC (Network Access Code) IDs to determine user access levels.
NAC ID System
NAC IDs control which zones/channels users can access and what permissions they have (Signal 100, control frequencies, etc.).
Basic Integration
Config.getUserNacId = function(serverId)
-- Example: Return NAC ID based on your framework
local player = YourFramework.GetPlayer(serverId)
if player and player.job then
if player.job.name == "police" then
return "100" -- Police NAC
elseif player.job.name == "ambulance" then
return "200" -- EMS NAC
end
end
return "000" -- Civilian/Default
end
Framework Examples
QBCore Integration
Config.getUserNacId = function(serverId)
local player = exports['qb-core']:GetPlayer(serverId)
if player and player.PlayerData.job then
local job = player.PlayerData.job.name
if job == "police" then return "100"
elseif job == "ambulance" then return "200"
end
end
return "000"
end
Config.getPlayerName = function(serverId)
local player = exports['qb-core']:GetPlayer(serverId)
if player and player.PlayerData.metadata.callsign then
return player.PlayerData.metadata.callsign
end
return GetPlayerName(serverId)
end
ESX Integration
Config.getUserNacId = function(serverId)
local xPlayer = ESX.GetPlayerFromId(serverId)
if xPlayer then
local job = xPlayer.getJob().name
if job == "police" then return "100"
elseif job == "ambulance" then return "200"
end
end
return "000"
end
Zone & Channel Configuration
Basic Zone Structure
Config.zones = {
[1] = {
name = "Statewide",
nacIds = { "100", "200" }, -- Who can access this zone
Channels = {
[1] = {
name = "DISPATCH",
type = "conventional",
frequency = 154.755,
allowedNacs = { "100" },
gps = {
color = 54,
visibleToNacs = { "100" }
}
}
}
}
}
Channel Types
Conventional Channels
{
name = "DISPATCH",
type = "conventional",
frequency = 154.755,
allowedNacs = { "100" },
scanAllowedNacs = { "110" },
gps = { color = 54, visibleToNacs = { "100" } }
}
Trunking Channels
{
name = "CAR-TO-CAR",
type = "trunked",
frequency = 856.1125, -- Control frequency
frequencyRange = { 856.000, 859.000 }, -- Available range
coverage = 500, -- Meters
allowedNacs = { "100" },
gps = { color = 25, visibleToNacs = { "100" } }
}
Permission System
-- Radio Access Control
Config.radioAccessCheck = function(playerId)
-- Control who can use radios
return true -- Allow everyone by default
end
Custom Alerts Configuration
Configure custom alerts that can be triggered from dispatch or in-game:
-- Alerts configuration, the first alert is the default alert for the SGN button in-game
alerts = {
[1] = {
name = "SIGNAL 100", -- Alert Name
color = "#d19d00", -- Hex color code for alert
isPersistent = true, -- If true, the alert stays active until cleared
tone = "ALERT_A", -- Corresponds to a tone defined in client/radios/default/tones.json
},
[2] = {
name = "SIGNAL 3",
color = "#0049d1", -- Hex color code for alert
isPersistent = true, -- If true, the alert stays active until cleared
tone = "ALERT_A", -- Corresponds to a tone defined in client/radios/default/tones.json
},
[3] = {
name = "Ping",
color = "#0049d1", -- Hex color code for alert
tone = "ALERT_B", -- Corresponds to a tone defined in client/radios/default/tones.json
},
[4] = {
name = "Boop",
color = "#1c4ba3", -- Hex color code for alert
toneOnly = true, -- If true, only plays tone without showing alert on radio
tone = "BONK", -- Corresponds to a tone defined in client/radios/default/tones.json
},
},
Alert Configuration Notes
- The first alert in the array is triggered by the SGN button in-game
- Users with the
dispatchNacId
can trigger the first alert from in-game isPersistent
alerts stay active until manually clearedtoneOnly
alerts play sound without visual notification
Custom Tones Configuration
Configure and customize alert tones using frequency-based tone generation:
{
"BEEP": [{ "freq": 910, "duration": 250 }],
"BONK": [{ "freq": 700, "duration": 300 }],
"ALERT_A": [
{ "freq": 600, "duration": 150, "delay": 200 },
{ "freq": 600, "duration": 150 }
],
"ALERT_B": [{ "freq": 500, "duration": 200 }],
"CUSTOM_TONE": [
{ "freq": 800, "duration": 100 },
{ "freq": 1000, "duration": 100, "delay": 50 },
{ "freq": 1200, "duration": 200 }
]...
}
Creating Custom Tones
Tones are defined as arrays of frequency objects with the following properties:
freq
- Frequency in Hz (20-20000)duration
- How long the tone plays in millisecondsdelay
- Optional delay before playing the next tone (milliseconds)
Radio-Specific Tone Overrides
You can override tones for specific radio types by placing a tones.json
file in individual radio layout folders:
client/radios/
├── default/
│ └── tones.json (Global default tones)
├── ATX-8000/
│ └── tones.json (Overrides for ATX-8000 radio)
└── XPR-6500/
└── tones.json (Overrides for XPR-6500 radio)
Tone Configuration
- Global tones are defined in
client/radios/default/tones.json
- Radio-specific overrides take priority over global defaults
- Tone names in alerts configuration must match JSON keys
Advanced Settings
Audio Configuration
Config = {
-- Volume Defaults
voiceVolume = 60, -- Voice chat volume (0-100)
sfxVolume = 20, -- Radio effects volume (0-100)
-- Sound Effects
playTransmissionEffects = true, -- Background sounds (sirens, guns)
analogTransmissionEffects = true, -- Static/analog effects
-- 3D Audio (Experimental)
default3DAudio = false, -- Enable nearby radio audio
default3DVolume = 50, -- 3D audio volume (0-100)
-- PTT Settings
pttReleaseDelay = 350, -- Delay before releasing PTT (ms)
}
Interference System
Config = {
bonkingEnabled = true, -- Enable radio interference
bonkInterval = 750, -- Interference interval (ms)
interferenceTimeout = 5000, -- How long interference lasts
blockAudioDuringInterference = true, -- Block voice during interference
}
Background Sound Detection
Config.bgSirenCheck = function()
local playerPed = PlayerPedId()
local vehicle = GetVehiclePedIsIn(playerPed, false)
if vehicle == 0 then return false end
-- Check if siren is on and vehicle is moving
local speed = GetEntitySpeed(vehicle) * 2.237 -- Convert to mph
return IsVehicleSirenOn(vehicle) and speed > 50
end
LVC Integration
If using Luxart Vehicle Control, add this export to LVC's cl_lvc.lua
:
exports('sirenCheck', function()
local playerPed = PlayerPedId()
local vehicle = GetVehiclePedIsIn(playerPed, false)
if not vehicle or vehicle == 0 then return false end
-- Check LVC siren states
local siren = false
if state_lxsiren[vehicle] and state_lxsiren[vehicle] > 0 then siren = true end
if state_pwrcall[vehicle] and state_pwrcall[vehicle] > 0 then siren = true end
if state_airmanu[vehicle] == true then siren = true end
return siren
end)
Then update your radio config:
Config.bgSirenCheck = function()
return exports["lvc"]:sirenCheck()
end
Player Animations
Configure multiple animation styles that users can select through radio settings:
-- Multiple Animation Configurations
-- Users can select which animation to use through radio settings
animations = {
[1] = { -- No animations
name = "None",
onKeyState = function(isKeyDown)
-- Empty function for no animations
end,
onRadioOpen = function()
-- Empty function for no animations
end
},
[2] = { name = "Shoulder", ... }, -- Police shoulder radio
[3] = { name = "Handheld", ... }, -- Physical radio prop
[4] = { name = "Earpiece", ... }, -- Earpiece/headset style
},
Each animation configuration includes:
name
- Display name for the animation styleonKeyState(isKeyDown)
- Function called when PTT key is pressed/releasedonRadioOpen()
- Function called when radio UI is opened
Animation Types Available
- None: Disables animations completely
- Shoulder: Uses police shoulder radio animation (
random@arrests
) - Handheld: Creates a physical radio prop with cellphone animation
- Earpiece: Simulates earpiece/headset usage with listen animation
Battery System Configuration
Configure how the radio battery system works:
-- Battery system configuration
-- This function is called every second to update the battery level
-- @param currentBattery: number - current battery level (0-100)
-- @param deltaTime: number - time since last update in seconds
-- @return: number - new battery level (0-100)
batteryTick = function(currentBattery, deltaTime)
local playerPed = PlayerPedId()
local vehicle = GetVehiclePedIsIn(playerPed, false)
if vehicle ~= 0 then
-- Charge battery when in vehicle
local chargeRate = 0.5 -- 0.5% per second
return math.min(100.0, currentBattery + (chargeRate * deltaTime))
else
-- Discharge battery when on foot
local dischargeRate = 0.1 -- 0.1% per second
return math.max(0.0, currentBattery - (dischargeRate * deltaTime))
end
end,
Battery System
- Customize how battery charges/discharges based on player state
- Can implement different rates for different scenarios
Custom Radio Layouts
Available Layouts
Config.radioLayouts = {
"AFX-1500", -- Mobile radio
"AFX-1500G", -- Marine mobile
"ARX-4000X", -- Compact handheld
"XPR-6500", -- Professional mobile
"XPR-6500S", -- Slim mobile
"ATX-8000", -- Advanced handheld
"ATX-8000G", -- Government handheld
"ATX-NOVA", -- Futuristic handheld
"TXDF-9100", -- Aviation radio
}
Auto Layout Assignment
-- Default layouts by vehicle type and spawn code
defaultLayouts = {
["Handheld"] = "ATX-8000", -- On foot (required)
["Vehicle"] = "AFX-1500", -- In ground vehicles (required)
["Boat"] = "AFX-1500G", -- In boats (required)
["Air"] = "TXDF-9100", -- In aircraft (required)
-- Vehicle-specific overrides by spawn code
["fbi2"] = "XPR-6500", -- FBI vehicles use XPR-6500 layout
}
Layout Assignment
- The first four vehicle types (Handheld, Vehicle, Boat, Air) are required
- You can override the default layout for specific vehicle spawn codes
- Vehicle-specific overrides take priority over vehicle type defaults
Creating Custom Layouts
- Copy existing layout folder from
client/radios/
- Rename to your custom name
- Replace assets:
radio.png
- Main radio imageicons/
folder - Button icons- Update
config.json
with positioning
Maintenance
Backup These Files
config.lua
- Main configuration- Custom radio layouts in
client/radios/
Update Process
- Backup current configuration
- Extract new resource files
- Restore configuration settings
- Test before going live
Security Reminder
Always change authToken
and dispatchNacId
from defaults before production use!