Multi-channel handheld radio for FiveM, built on pma-voice. Players stay connected to up to four channels at once, split every channel into rooms for private talks and control everything on the touch screen of an original XR handheld (NUI, no DUI). A channel overlay shows who is on the channel and who is talking.
- Up to 4 channels at the same time (up to 16 by config), per-channel microphone, speaker and volume
- Rooms A-D inside every channel: the whole channel stays listed, only the own room is heard (private talks without changing the frequency); or four push-to-talk groups (A-D) instead
- Frequencies, named channels, password channels, job/grade/duty/ACE/Discord-role protected channels
- Protected police channels 1-4 with optional server passwords
- One priority speaker per channel, transferable; optional half duplex (one speaker per room)
- Member list with call signs and colours, per-member volume and mute, focus listening, three saved listening mixes
- Emergency key with map blip, unit status with acknowledgements, speaking queue, temporary invite-only team channels
- In-game channel editor and announcements for admins (ACE)
- First-open setup and personal settings saved per character (oxmysql or files)
- Radio voice effects (digital, analog, tactical, poor signal), click and roger sounds, optional range and towers
- Custom XR handheld model with built-in animations, mg-radioanim support or your own animation resource
- Four device finishes, 14 languages, XR Jammer support
- Qbox, QBCore, ESX and standalone; ox_inventory, qs-inventory, tgiann-inventory, codem-inventory, core_inventory, origen_inventory, the framework inventory (qb-inventory, ps-inventory, ESX) or no item at all
Requirements
- FiveM server artifact with Lua 5.4 support (any current artifact)
- pma-voice (6.6 or newer)
- Optional: Qbox, QBCore or ESX Legacy; one of the inventories listed above; oxmysql (personal settings in the database); OneSync (range, towers and emergency blips)
Installation
- Put the
xr-radiofolder into yourresourcesfolder. Keep the folder namexr-radio. - Stop your old radio script (for example qb-radio, qbx_radio, ac_radio, mm_radio, rp-radio). Two radio scripts on the same item or the same key would fight each other.
- Add the radio item for your inventory, see Items.
- Start XR Radio after your framework, inventory and pma-voice in
server.cfg:
ensure pma-voice
ensure xr-radio
- Set your language and options in
config.lua(Config.Locale = 'en'). - Give admins the editor permission if you want it:
add_ace group.admin xr.radio.admin allow - Restart the server.
Framework and inventory are detected automatically (Config.Framework = 'auto', Config.Inventory = 'auto').
Items
By default a player needs one of these items. Each item selects a radio profile (see Config.Profiles):
| Item | Profile | Who can use it |
|---|---|---|
radio |
standard | everybody |
radio_police |
police | job police, on duty |
radio_ambulance |
ambulance | job ambulance, on duty |
radio_mechanic |
mechanic | job mechanic |
You only need the items you want to use. Copy install/radio.png to the image folder of your inventory.
ox_inventory (Qbox, QBCore or ESX with ox_inventory)
Copy the entries of install/ox_inventory.lua into ox_inventory/data/items.lua. ox_inventory already ships a
radio entry: replace it with ours (an item can only have one entry). consume = 0 keeps the radio in the inventory.
The export xr-radio.useRadio names the resource folder, so the folder must be called xr-radio.
Image folder: ox_inventory/web/images/.
QBCore with qb-inventory, ps-inventory or lj-inventory
Copy the entries of install/qb-core.lua into qb-core/shared/items.lua (replace an existing radio entry).
XR Radio registers the items as usable itself. Image folder: qb-inventory/html/images/ (or the folder of your inventory).
ESX default inventory
Run install/esx.sql on your database (it skips items that already exist).
qs-inventory, tgiann-inventory, codem-inventory, core_inventory, origen_inventory
Add an item with the same name the way your inventory documents it (usable, not consumed). XR Radio registers the use through qs-inventory or through your framework and reads item counts from the inventory.
No items
Set Config.Access.mode = 'key': everybody allowed by Config.Access.keyRule opens the radio with F6 or /radio.
On a server without any inventory 'auto' already works this way.
Using the radio
| Key / command | Action |
|---|---|
F6, /radio or using the item |
open or close the radio |
| Left Alt (hold) | talk on your channels while the radio is closed |
| Extra PTT keys (B, C, D) | optional; assign them in Settings > Key Bindings > FiveM, or on the radio |
/freq 25.30 |
connect to a frequency, /freq 25.30 password for a password channel |
/freqoff |
disconnect from every channel |
/radioclicks |
microphone click sounds on or off |
| Side PTT button | talk while the radio is open |
| Orange key (hold) | emergency alert to everybody on your channels |
| Escape / power button | close the radio |
Channels
- Type a frequency or a channel name (PD1, EMS1, ...) on the keypad and connect. The first player on a new frequency can protect it with a password. Connecting opens the channel page; opening the radio while connected shows the page of your last channel, and the menu at the bottom leads to everything else.
- Every channel has a microphone switch, a speaker switch and a volume. The PTT key talks on every channel whose microphone is on.
- Channel details: members, call sign and colour, per-member volume and mute, focus listening (hear only this channel), lock, owner and priority transfer.
Rooms
Everybody joins room A of a channel. A player moves to room B, C or D with the buttons on the channel page. The whole
channel stays listed (members of other rooms are greyed out with their room), but only members of the same room hear
each other and see each other talking. The overlay shows your room next to the frequency when it is not A.
With Config.Rooms.enabled = false the same buttons choose one of four push-to-talk groups instead (a PTT key then
talks on the channels of its group).
Settings and keys
- Settings: device finish, size, screen colours, sounds, animation, PTT keys, channel overlay (position, only speakers, transparent list with colour, name and talking icon only), listening mixes. Press Save changes at the bottom.
- PTT keys can also be set on the radio: letters, digits, F-keys, Space, Caps Lock, Shift, Ctrl, Alt, arrows and similar keyboard keys (mouse buttons are not supported there).
- Players closer than
Config.Voice.naturalDistancehear each other's normal voice instead of the radio.
The radio takes the mouse while it is open. With Config.Device.allowMovement = true players can walk while it is open.
Configuration
Everything is in config.lua, with a comment next to each option. The most used parts:
| Section | What it controls |
|---|---|
Locale, Framework, Inventory |
language and integrations |
Access, Profiles, Items |
who can use a radio, with which item, how many channels |
Frequency, Channels |
frequency range, restricted ranges, named channels |
PoliceChannels, ChannelPolicies |
protected numeric and named channels |
Modules |
teams, status, speaking queue, diagnostics, personal settings, channel editor |
Talk |
transmission limit, routing buckets, full or half duplex |
Rooms |
rooms inside every channel (on/off, room names) |
Keys, Commands |
default keys and command names |
Voice |
pma-voice resource, volumes, radio effect presets |
Hud, Audio, Personalization |
overlay defaults (position, transparent list; the overlay follows the screen height), sounds, choices in the settings |
Animation, Prop, Gates |
animations, the radio model, when the radio cannot be used |
Signal |
optional range by distance or towers |
Emergency, Jammer, Admin, Logging, Discord |
alerts and integrations |
Access rules
Profiles, channels and policies use these rules:
rule = { jobs = { police = 0, sheriff = 2 }, onDuty = true } -- job name = minimum grade
rule = { ace = 'xr.radio.swat' } -- ACE permission
rule = { discordRoles = { '123456789012345678' } } -- needs Config.Discord
rule = { custom = 'gang' } -- Editable.CustomAccess.gang(src) in server/editable.lua
rule = { jobs = { police = 0 }, ace = 'xr.radio.swat', mode = 'any' } -- one of them is enough
Restricted frequency range
For example 1.00 - 9.99 for police and EMS:
Frequency = { min = 1.0, max = 999.99, step = 0.01, restricted = {
{ min = 1.0, max = 9.99, rule = { jobs = { police = 0, ambulance = 0 }, mode = 'any' } }
} },
Your own click sound
Put mytone_on.wav and mytone_off.wav into html/sounds/, then add the pack and list it
first to make it the default for new players:
Audio = { enabled = true, volume = 0.3, roger = 'soft', clickPacks = {
mytone = { label = 'My radio', on = 'mytone_on', off = 'mytone_off' },
-- tactile, digital, classic ...
} },
-- in Personalization:
clickPacks = { 'mytone', 'tactile', 'digital', 'classic' },
Server convars
Secrets never go into config.lua (players receive that file). Put them into server.cfg with set:
set xr_radio_police_1_password "change-me" # optional, channel 1 (also 2, 3, 4)
set xr_radio_webhook "https://discord.com/api/webhooks/..." # Config.Logging.enabled = true
set xr_radio_discord_token "your bot token" # Config.Discord.enabled = true
Use set, not setr (setr would send the value to every player).
Protected channels
Channels 1-4
These channels (Config.PoliceChannels) are for the jobs in PoliceChannels.jobs (default: police). 1, 1.0
and 1.00 are the same channel. Without a password convar the job is enough; with
set xr_radio_police_1_password "..." the player needs the job and the password. Nobody can take these channels over
or change their password in game.
More protected channels
Add them to Config.ChannelPolicies.definitions:
definitions = {
[25.50] = { name = 'Medical', rule = { jobs = { ambulance = 0 } }, passwordConvar = 'xr_medical_password', priorityJob = 'ambulance', preset = 'digital' },
SWAT = { name = 'SWAT', rule = { ace = 'xr.radio.swat' }, preset = 'tactical' }
}
passwordConvar is optional, as for channels 1-4.
Priority speaker
Every channel has one. On normal channels it is the first member; on protected channels the
highest grade of priorityJob (police on channels 1-4). The current priority speaker can hand it to another member.
The priority speaker is heard a little louder (Config.Voice.priorityBoost) and, on a half duplex channel, can talk
over the others (their transmission stops).
Administration
Players with the Config.Admin.ace permission (xr.radio.admin) get an administration page on the radio: lock
channels, remove members, send announcements and create or edit protected channels in game (saved on the server,
kept over restarts). Channels from config.lua cannot be changed in game.
Animations and the radio model
The radio uses the custom xr_radio_01 model (streamed by the resource) with built-in animations. Players choose
handheld, shoulder or chest in the radio settings.
- mg-radioanim: set
Config.Animation.provider = 'mg-radioanim'. Its talking animation is then used (players pick one with/radioanim); holding the open radio stays built in. - Your own animation resource: set
provider = 'custom'andresource = 'your_resource', then fill inEditable.RadioAnimationStart/RadioAnimationStopinclient/editable.lua. - More animations in the radio settings: any animation dictionary that is loaded on your server (also the files of an animation pack) can be added to the list players choose from. The choice is saved per character:
-- Config.Animation
catalog = { ..., chestmic = { label = 'Chest mic', hold = 'hold', talk = 'chestmic' } },
poses = { ..., chestmic = { { dict = 'anim@cop_mic_pose_001', name = 'chest_mic', flag = 49 } } },
-- Config.Prop: hide the radio for mic poses, or give the pose its own attach entry
hiddenPoses = { ..., chestmic = true },
-- Config.Personalization
animations = { 'auto', 'hand', 'shoulder', 'chest', 'chestmic' },
Model position in the hand
Set Config.Debug = true, restart, open the radio and use
/xrradio_calib x y z rx ry rz [pose] (metres and degrees, pose hold, talk_hand or vehicle). Copy the values
printed in the F8 console into Config.Prop.attach, set Debug = false and restart.
Personal settings storage
Setup choices, keys, favourites and listening mixes are saved per character on the server.
Config.Persistence.driver = 'auto' uses oxmysql when it is running (the table is created automatically) and files in
xr-radio/server_data/ otherwise. The first start remembers the driver; to switch later, migrate the data first.
Keep server_data/ when you update the resource (file driver).
Integrations
- pma-voice: XR Radio uses its own voice targets and leaves proximity and phone calls to pma-voice. Do not use the pma-voice radio of another script at the same time. Players in a phone call with each other keep the call audio.
- XR Jammer: jammed players cannot transmit (
Config.Jammer). - Dispatch: fill
Editable.OnEmergencyinserver/editable.lua, or listen to the server eventxr-radio:emergency. - HUDs:
exports['xr-radio']:GetHudState()on the client, and pma-voice'sradioActivestate while talking.
Developer API
Server exports (exports['xr-radio']:Name(...)):
| Export | Description |
|---|---|
JoinChannel(src, channel, options) |
connect a player (options.password) |
LeaveChannel(src, channel), LeaveAll(src) |
disconnect |
GetPlayerChannels(src) |
channels of a player |
GetChannelMembers(channel), IsInChannel(src, channel) |
members |
IsTransmitting(src) |
is the player talking |
CreateChannel(channel, options), RemoveChannel(channel), SetChannelLocked(channel, locked) |
channels |
SetBlocked(src, reason, state) |
block a player's radio for your own reason (e.g. 'prison') |
TriggerEmergency(src, channel) |
send an emergency alert |
Client exports: Open(), Close(), IsOpen(), Join(channel, password), Leave(channel), LeaveAll(),
GetChannels(), IsConnected(), IsTransmitting(), SetChannelVolume(channel, volume), SetHudVisible(state),
SetDisabled(reason, state), GetHudState().
Server events: xr-radio:joined (src, channel), xr-radio:left (src, channel, reason),
xr-radio:txStarted (src, channels), xr-radio:txEnded (src), xr-radio:emergency (src, channels, coords),
xr-radio:roomChanged (src, channel, room), xr-radio:log (kind, src, channel).
Client events: xr-radio:opened, xr-radio:closed, xr-radio:txChanged (talking),
xr-radio:channelsChanged (channels), xr-radio:talkersChanged (channel, talkers).
Channels are written as frequencies ('25.30') or names ('PD1'); events report keys like f:25300 (frequency in thousandths) and n:pd1.
Troubleshooting
| Problem | Check |
|---|---|
| "You need an allowed radio item" | item name in your inventory matches Config.Items / the profile items; the job rule of the profile |
| Using the item does nothing | the old radio script is still running; the ox_inventory entry has export = 'xr-radio.useRadio' |
| Channel joined, nobody hears me | same channel and the same room; microphone on; pma-voice running; jammer |
| I hear nothing | speaker on, volumes, focus listening, muted member, Config.Voice.resource |
| Channel 1-4 refuses police | PoliceChannels.jobs, requireDuty, the password convar |
| Standard GTA radio in the hand | the stream folder is complete; F8 console for model warnings |
| Radio sits wrong in the hand | calibrate with /xrradio_calib, see above |
| Settings are not saved | oxmysql running, or the server can write xr-radio/server_data/ |
Set Config.Debug = true and check the server console and F8 for lines starting with [xr-radio].
Changelog
1.0.0
First release.
- Multi-channel radio on pma-voice: up to 4 channels at once (16 by config), per-channel microphone, speaker and volume
- Rooms A-D inside every channel for private talks (everyone stays listed, only the own room is heard), or four push-to-talk groups instead; the radio opens on the current channel's page
- Frequencies, named channels, password channels and protected channels by job, grade, duty, ACE or Discord role; police channels 1-4 with optional server passwords
- Priority speaker per channel, half or full duplex, emergency alerts with blips, unit status, acknowledgements, speaking queue, temporary team channels, in-game channel editor for admins
- Member call signs and colours, per-member volume and mute, focus listening, saved listening mixes
- First-open setup and personal settings saved per character (oxmysql or files)
- Custom XR handheld model, built-in animations, mg-radioanim and custom animation providers
- Radio voice effects, optional range and towers, XR Jammer support
- Qbox, QBCore, ESX, standalone; ox_inventory, qs-inventory, tgiann-inventory, codem-inventory, core_inventory, origen_inventory and framework inventories
- 14 languages: en, de, fr, es, pt-br, pl, nl, it, tr, cs, da, sv, hu, ro
