Setup & Configuration
Complete installation and configuration guide for Tommy's Radio system.
Setup & Configuration
Prerequisites
- FiveM Server
- Firewall configuration access
Installation
1. Download and Extract
- Download the radio resource files
- Extract to your server's
resources
folder - Open the
fxmanifest.lua
file in the resource folder. - Make sure the fx_version is set to
bodacious
, otherwise it will not work!
Important
fx_version 'bodacious'
2. Start the Resource
Add to server.cfg
:
start radio
Resource Naming
It is important to keep the resource name to "radio" for export usage.
3. Voice Server / Dispatch Panel Setup
The radio system includes its own voice server:
- Port: Default port 7777 (configurable in config.lua)
- Auto-start: Voice server starts automatically with the resource
- Access: Dispatch panel accessible at
http://your-server-ip:your-port
4. Configure Firewall
Ensure the serverPort (as configured in config.lua) is open / exposed on the host server.
# Ubuntu/UFW
sudo ufw allow 7777
# Windows
netsh advfirewall firewall add rule name="Radio Voice Server" dir=in action=allow protocol=TCP localport=7777
Server Configuration
This process may vary based on your server's operating system, yet should be fairly easy to perform.
5. Dispatch Panel
At this point, the radio should be functional, and the dispatch panel should be accessible at http://your-server-ip:your-port
, however, most modern browsers block microphone access for insecure URLs. For this reason, we provide a desktop app, which supports global hotkeys and will work without further setup.
6. Web-Based Setup (Optional)
Since most modern browsers block microphone access for insecure URLs, there are two options:
Bypass Browser Block (Simple)
Most modern browsers allow users the ability to bypass the microphone access block on configured sites using the unsafely-treat-insecure-origin-as-secure
flag. You could have your dispatchers set this flag for your dispatch panel site, and everything should work as normal.
SSL/HTTPS Setup (Advanced / Recommended)
Production Use (HTTPS Recommended)
We recommend using HTTPS for dispatch panel microphone access. You have two options:
- SSL Certificate for IP
- Get an SSL certificate for your IP address.
- Set
config.lua
connection string to:https://your-server-ip:your-port
- Reverse Proxy with Domain
- Use an SSL-certified domain (e.g., via Nginx or Apache) to proxy requests to the voice server/dispatch panel port.
- Set
config.lua
connection string to:https://proxy.example.com
External Setup
As many systems have different configurations & distributions, we do not provide support for setting this up - as this part is outside of the resource's scope. There are plenty of videos on YouTube that can assist with this process, and even AI tools like ChatGPT can help guide you through it.
Basic Configuration
config.lua
-- Network Configuration
connectionAddr = "", -- Final connection string for clients, Can include protocol (https://proxy.example.com) and/or port. (If empty, uses host ip address and port)
serverPort = 7777, -- Port for radio server & dispatch panel, choose a port that is not used by other resources.
authToken = "changeme", -- Secure token for radio authentication, change this to a secure random string.
dispatchNacId = "141", -- NAC ID / Password for dispatch channel, change this to your desired ID.
-- Control Keys
controls = {
talkRadioKey = "B",
toggleRadioKey = "F6",
channelUpKey = "",
channelDownKey = "",
zoneUpKey = "",
zoneDownKey = "",
menuUpKey = "",
menuDownKey = "",
menuRightKey = "",
menuLeftKey = "",
menuHomeKey = "",
menuBtn1Key = "",
menuBtn2Key = "",
menuBtn3Key = "",
emergencyBtnKey = "",
closeRadioKey = "",
powerBtnKey = "",
},
-- Audio settings
voiceVolume = 60, -- Default voice volume (0-100), can be changed in radio settings menu
sfxVolume = 20, -- Default sfx volume (0-100), can be changed in radio settings menu
playTransmissionEffects = true, -- Play background sound effects (sirens, helis, gunshots)
analogTransmissionEffects = true, -- Play analog transmission sound effects (static during transmission)
pttReleaseDelay = 350 -- Delay in milliseconds before releasing PTT to prevent cut-off (250-500ms recommended)
panicTimeout = 60000 -- 1 minute
-- 3D Audio settings (EXPERIMENTAL)
default3DAudio = false, -- true = earbuds OFF by default (3D audio enabled), false = earbuds ON by default (3D audio disabled)
default3DVolume = 0, -- Default 3D audio volume (0-100), saved per user like voice/sfx volume, default is 50
Security Settings
Change these values for production:
authToken = "your-secure-token-here" -- Use a strong random string
dispatchNacId = "your-dispatch-password" -- Dispatch panel access code
Available Radio Layouts
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
}
Radio Layouts
This defines the layouts the radio will use, you can find them in the /client/radios
folder.
Auto Layout Assignment
defaultLayouts = {
["Handheld"] = "ATX-8000", -- When on foot
["Vehicle"] = "AFX-1500", -- In ground vehicles
["Boat"] = "AFX-1500G", -- In boats
["Air"] = "TXDF-9100", -- In aircraft
}
Default Layouts
These are the default layouts based on the player's state. These can be changed in the radio interface.
Framework Integration
NAC ID System
NAC ID System
The permissions system uses NAC (Network Access Code) id's, based on a player's NAC ID, the system determines what zones/channels that user has access to, as well as if they can activate signals or switch to control frequencies.
Implement getUserNacId
function:
Standalone Example
getUserNacId = function(serverId)
-- Return NAC ID based on your logic
-- Example: static assignment
if serverId == 1 then
return "100" -- Police
elseif serverId == 2 then
return "250" -- EMS
end
return "000" -- Default/Civilian
end
Framework Example
getUserNacId = function(serverId)
-- Get player data from your framework
local player = YourFramework.GetPlayer(serverId)
if player then
local job = player.job.name -- Adjust for your framework
if job == "police" then
return "100"
elseif job == "ambulance" then
return "250"
end
end
return "000" -- Civilian
end
Player Name Display
getPlayerName = function(serverId)
local name = GetPlayerName(serverId)
-- You could extract callsign here if needed
return name
end
Radio Access Control
radioAccessCheck = function(playerId)
-- Control who can use the radio system
-- Return true to allow access, false to deny
return true -- Allow everyone by default
end
Signal 100 & Control Frequency Permissions
-- NAC IDs that can activate Signal 100
signalNacIds = {
"100", -- Police
"141", -- Dispatch
}
Audio Configuration
-- Interference settings
bonkingEnabled = true -- Enable radio interference
bonkInterval = 750 -- Interference interval (ms)
interferenceTimeout = 5000 -- How long interference lasts (ms)
blockAudioDuringInterference = true -- Block voice during interference
-- Return true to include background siren in transmission, false otherwise
bgSirenCheck = function()
local v = GetVehiclePedIsIn(PlayerPedId(), false)
return v and IsVehicleSirenOn(v) and GetEntitySpeed(v) * 2.237 > 50
end,
Siren Detection
Since FiveM does not have a native to check weather only the siren is on, by default we enable the siren if the lights are on and the vehicle is going more than 50mph.
LVC Integration for Siren Check
If you are using Luxart Vehicle Control, you can make the siren check more accurate by placing the following code snippet at the bottom of the UTIL/cl_lvc.lua
file in the luxart resource:
exports('sirenCheck', function() -- Returns true if the siren is on
local siren = false
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
if state_lxsiren[vehicle] then
if state_lxsiren[vehicle] > 0 then
siren = true
end
end
if state_pwrcall[vehicle] then
if state_pwrcall[vehicle] > 0 then
siren = true
end
end
if state_airmanu[vehicle] then
if state_airmanu[vehicle] == true then
siren = true
end
end
return siren
end)
After this, you may configure the bgSirenCheck function as such:
bgSirenCheck = function()
return exports["lvc"].sirenCheck()
end,
Animations Configuration
-- Called when push-to-talk key is pressed or released.
-- Plays animation if enabled. You can return early or empty this function to disable animations.
-- Returns nothing.
onKeyState = function(isKeyDown)
local ped = PlayerPedId()
if not ped or ped == 0 then return end
if isKeyDown then
-- Start talking: play radio animation
RequestAnimDict('random@arrests')
Citizen.Wait(100)
if HasAnimDictLoaded("random@arrests") then
if not IsEntityPlayingAnim(ped, "random@arrests", "generic_radio_enter", 3) then
TaskPlayAnim(ped, "random@arrests", "generic_radio_enter", 8.0, 2.0, -1, 50, 2.0, false, false, false)
end
end
else
-- Stop talking: stop radio animation
StopAnimTask(ped, "random@arrests", "generic_radio_enter", -4.0)
end
end,
-- Called when the radio UI is opened.
-- Loads the animation dictionary so it's ready when the user talks.
-- Returns nothing.
onRadioOpen = function()
local ped = PlayerPedId()
if not ped or ped == 0 then return end
RequestAnimDict('random@arrests')
end,
Player Animations
Here you can customize how the player animations work for the radio.
Zone and Channel Setup
Basic Zone Configuration
zones = {
[1] = {
name = "Statewide",
nacIds = { "100", "250", "275" }, -- Who can access this zone
Channels = {
[1] = {
name = "DISP", -- Channel name
type = "conventional", -- Channel type (trunked or conventional)
frequency = 154.755, -- Frequency in MHz
allowedNacs = { "100" }, -- Allowed NAC IDs for this channel (can connect and scan)
scanAllowedNacs = { "110", "200" }, -- NAC IDs that can only scan this channel (cannot connect)
gps = {
color = 1, -- What color blips should this channel have (Reference: https://docs.fivem.net/docs/game-references/blips/#blip-colors)
visibleToNacs = { 100, 250, 275 } -- Who can see this channel's blips
}
},
[2] = {
name = "TAC-1",
type = "conventional",
frequency = 154.755, -- Frequency in MHz
allowedNacs = { "100" }, -- Allowed NAC IDs for this channel (can connect and scan)
scanAllowedNacs = { "110", "200" }, -- NAC IDs that can only scan this channel (cannot connect)
gps = {
color = 2,
visibleToNacs = { 100 }
}
}
}
}
}
Trunked Channel Example
[3] = {
name = "C2C", -- Channel name
type = "trunked", -- Channel type
frequency = 856.1125, -- Control frequency
frequencyRange = { 856.000, 859.000 }, -- Range of available frequencies
coverage = 500, -- Coverage radius in game units
allowedNacs = { "100" }, -- Allowed NAC IDs for this channel (can connect and scan)
scanAllowedNacs = { "110", "200" }, -- NAC IDs that can only scan this channel (cannot connect)
gps = { visibleToNacs = { 100 } }
}
GPS Configuration
gps = {
color = 52, -- Blip color (GTA V blip colors)
visibleToNacs = { 100, 250 } -- NAC IDs that can see GPS blips
}
Example QBCore Configuration
Custom Radio Layouts
Creating a Custom Layout
- Copy existing layout from
client/radios/
- Rename folder (e.g.,
my-custom-radio
) - Replace assets:
radio.png
- Main radio imageicons/
folder - Button and indicator icons- Custom fonts (optional)
Layout Configuration
Edit config.json
in your custom radio folder:
{
"toneSet": "default",
"font": "Arial.ttf",
"fontSize": 12,
"fontWeight": "normal",
"radioWidth": 601,
"radioHeight": 234,
"radioBGColor": "#e8e8e9",
"buttons": [
{
"key": "power",
"x": 78, "y": 78,
"width": 25, "height": 25
}
]
}
Add to Configuration
Add your custom radio to config.lua:
radioLayouts = {
"AFX-1500",
"my-custom-radio", -- Your custom radio
}
Testing
Basic Testing
- Start server after configuration changes
- Check console for error messages
- Test connection with a client
- Verify push-to-talk functionality
Debug Mode
Enable verbose logging:
logLevel = 4 -- Enable verbose logging
Backup and Maintenance
Configuration Backup
Backup these files:
config.lua
- Main configuration- Custom radio layouts in
client/radios/
Updates
When updating:
- Backup current configuration
- Clear existing resource folder
- Extract new files
- Check for any updates to the configuration & restore backup configuration
- Test before going live