FIELD MANUAL // Player tools

OTM Radio

Documentation from the supplied script archive. Check your included resource files for the version you install.

otm_radio

Server-authoritative police radio for pma-voice. One resource, four targets: QBX, QBCore, ESX, standalone (auto-detected).

Features

  • Encrypted frequency ranges per job / job type / grade / duty (LEO, Command, EMS, Joint by default)
  • Every encrypted frequency is registered as a pma-voice channel check, so a modded client triggering pma-voice:setPlayerRadio directly still can't get in
  • Server sweep (default 3s) removes anyone who loses the job, goes off duty, drops the radio item or goes down
  • Unit list with callsigns + live talking indicators on every channel (open/civilian channels too; Config.Ui.showPublicMembers = false hides names there)
  • On-screen unit list overlay (/radiolist, keybind, or radio Settings > Unit List): stays up with the radio put away; drag the header to move, the corner to resize (while the radio is open or via /radiolist edit); saved per player. Text-only style (no background): the square toggle in the list header, /radiolist bg, or Config.Roster.background = false as the default.
  • Resizable radio: Settings > Radio Size (left / right or -/+), saved per player; Config.Ui.scale is the starting size See Config.Roster
  • Panic button (hold-to-trigger in UI, /panic, keybind): server-side coords, cooldown, flashing blip + sound to on-duty units
  • Callsigns: QB/QBX read/write metadata.callsign (stays in sync with PD scripts); ESX/standalone use oxmysql or KVP
  • Presets filtered by what the player can access; next/prev channel keybinds
  • Hold / talk animations with radio prop, volume + mic-click prefs saved per client
  • Rate limiting on every server callback, input sanitised server-side, all NUI text rendered with textContent

Requirements

  • ox_lib, pma-voice (with setr voice_enableRadios 1)
  • Optional: oxmysql (callsign storage on ESX/standalone; KVP fallback otherwise)
  • Inventory: ox_inventory, qb-inventory, qs-inventory, ESX default, or none

Install

  1. Drop otm_radio into your resources folder.
  2. Stop any other radio resource that drives pma-voice (qbx_radio, qb-radio, mm_radio, etc.). QBX ships qbx_radio by default.
  3. server.cfg, after pma-voice and your framework: ensure ox_lib ensure pma-voice ensure otm_radio
  4. Add the item (below). Done — the SQL table auto-creates if oxmysql is running.

Item

ox_inventory (ox_inventory/data/items.lua):

['radio'] = {
    label = 'Radio',
    weight = 500,
    stack = false,
    close = true,
    client = { export = 'otm_radio.useRadio' },
},

qb-core (qb-core/shared/items.lua):

radio = { name = 'radio', label = 'Radio', weight = 500, type = 'item', image = 'radio.png', unique = true, useable = true, shouldClose = true, description = 'Handheld radio' },

ESX: run the INSERT in sql/otm_radio.sql.

Don't want an item? Config.Item.required = false and use /radio or the keybind.

Standalone jobs (ACE)

add_ace group.police otm_radio.job.police allow
add_ace group.police_command otm_radio.job.police_command allow
add_ace group.ems otm_radio.job.ambulance allow
add_principal identifier.license:xxxx group.police

Config notes

  • Restricted ranges must not overlap. jobs matches job name, jobTypes matches QBX/QB job type (leo, ems), value = minimum grade.
  • Frequency.decimals = 2 locks 100 frequencies per whole number in a range (default ranges = 900 locks, registered once at start). If you widen ranges a lot, lower decimals to 1.
  • Death detection: QB/QBX use metadata.isdead / inlaststand. ESX/standalone use the isDead statebag or 0 health. If your ambulance job uses something else, edit stateDead in bridge/server/framework.lua.
  • Keybinds default to unbound; players set them in Settings → Key Bindings → FiveM.

Exports

Server:

exports.otm_radio:getPlayerChannel(src)          -- number (0 = none)
exports.otm_radio:getChannelMembers(freq)        -- { id, name, callsign, talking }[]
exports.otm_radio:kickFromRadio(src, reason?)    -- reason = locale key, e.g. 'removed'
exports.otm_radio:canJoinFrequency(src, freq)    -- boolean

Client:

exports.otm_radio:openRadio()
exports.otm_radio:closeRadio()
exports.otm_radio:isOpen()
exports.otm_radio:isPowered()
exports.otm_radio:getFrequency()

Extending

  • New framework: add a branch in bridge/server/framework.lua (getRawPlayer, getPlayerData, events) and bridge/client/framework.lua (onUnload).
  • New inventory: add a branch in bridge/server/inventory.lua → hasItem.
  • New language: copy locales/en.lua to locales/xx.lua, change Locales.en to Locales.xx, set Config.Locale.