Usage & Customization

In-game controls, admin panel, dispatch panel, alerts, animations, layouts, sounds, and dispatch theming for Tommy's Radio.

In-GameAdmin PanelDispatchTheming

In-Game Radio

Controls

ButtonFunction
ZNSelect zone
CHBrowse channels
STOpen settings
CONConnect to channel
DSCDisconnect from channel
SCNToggle scanning
SGNTrigger channel alert (dispatchers / tlib.admin only)

Hold B (default) to transmit. PTT is blocked if the radio is off, the player is dead or swimming, Config.canTalk returns false, or another user is transmitting and blockTransmission is on.

Settings Menu

ST opens the quick settings panel. /tradio opens the full settings dialog with additional options.

SettingNotes
GPSToggle your location blip visibility
EarbudsOff = your radio leaks out spatially; On = private earpiece mode
VC / SFX / 3D VolPer-player volume for voice, tones, and nearby radios (0–100)
StyleSwitch radio model (also selectable per context in /tradio)
DISPTheme: Auto, Light, or Dark
MoveDrag to reposition, scroll to scale (0.5×–1.5×). RST resets saved position
MICSelect microphone device
AnimRadio use animation style

Additional options in /tradio:

OptionNotes
Radio Style per contextSet separate models for on-foot, vehicle, boat, aircraft
Separate On-Foot Focus LayoutSave a different model for focused vs. unfocused on-foot states
Channel ListEnable the floating channel list overlay
Active Talkers OnlyShow only transmitting users in the channel list
Scan ListsLoad a preconfigured scan list from your codeplug (only when scan lists are defined)
Move Channel ListDrag/scale the channel list overlay independently
User settings dialog — all options visible

3D Prop Editor

Admins with tlib.admin can place a dashboard radio prop per vehicle model. Open from /tradio3D Prop Editor. You must be seated in the vehicle you are editing.

3D Prop Editor
InputAction
Middle mouse + dragOrbit
Shift + middle mouse + dragPan
Scroll wheelZoom

Drag coloured arrows (translate) or rings (rotate). W = translate, R = rotate, X = toggle world/local space. Save applies immediately to all clients. Export Config copies the placement block for use in another resource.

Vehicle radio prop placed in dashboard

Channel Features

Scanning

Navigate to a channel and press SCN to add it to your scan list (press again to remove). Your connected channel wins transmit priority. Transmitting on your primary mutes scan traffic until PTT release.

During a received scan transmission the display shows SC: followed by the transmitting unit's callsign, name, and agency.

Scan-received transmission display

Channel List Overlay

Enable via /tradioChannel List. The overlay is patch-aware, trunked-aware, and scan-talker-aware.

  • Callsign badges colored by the channel's GPS blip color
  • Dispatchers labeled with a DSP badge; panicking units highlighted in red
  • Header badges: zone/channel name, frequency, P for patched, T for trunked, active alert name, PANIC during emergencies

Emergency / Panic

Press emergencyBtnKey or the on-radio emergency button. GPS blip flashes red for everyone whose NAC ID is in the channel's gps.visibleToNacs. Auto-clears after panicTimeout (default 60 s — configure in Admin Panel → Settings → Advanced tab).

SGN button fires the first alert in your configured list on the connected channel. Available to players with tlib.admin ACE or a codeplug with permissions.supervisor = true. Press again to clear.

Bonking (Transmission Collision)

Keying up while someone else is transmitting plays a bonk tone. With blockTransmission on, the second PTT is suppressed. Double-tapping PTT within doubleTapWindow ms forces it through regardless. Configure in admin panel → Settings → Bonking.

Battery & Signal

Battery shows as 0–100%. Defaults: charges 0.5%/s in a vehicle, drains 0.1%/s on foot; at 0% the radio powers off. Override via Config.batteryTick.

Signal bars (1–5) reflect proximity to the nearest configured tower. When Signal Degradation is enabled, low signal degrades voice audio quality.

3D Audio

When enabled, nearby players hear your radio output (voice, tones, sirens, heli, gunshots) spatially. Range: 100 m (person) or 200 m (vehicle source).

Earbuds toggle controls your outbound 3D broadcast. Off = sound leaks out spatially. On = private earpiece mode, no spatial broadcast.

With vehicleRadio3DAudioEnabled on, entering a vehicle claims it as a 3D radio source. Walking more than vehicle3DActivationDistance m away hands the source role to the vehicle itself (200 m range). One vehicle, one owner at a time.

Callsigns

With useCallsignSystem on, players set a callsign that replaces their game name on the radio display and in the dispatch panel.

/callsign 2L-319   -- Set
/callsign          -- Clear

Callsigns persist in KVP, namespaced by tlib_community_id. Set sets tlib_community_id "your-unique-id" in server.cfg to prevent callsigns bleeding between test and production instances that share the same sv_projectName. Dispatchers can assign callsigns remotely via the dispatch panel or exports['tRadio']:setCallsign(serverId, callsign).


Admin Panel

Admin Panel

Served at http://your-server-ip:port/admin. All changes broadcast to connected clients instantly and persist to data.json. Sessions expire after 30 minutes of inactivity.

Setup & Authentication

config/config.lua
Config = {
    adminPassword = "your_secure_password",  -- Password login

    -- Discord OAuth (optional — can use both)
    discord = {
        adminRoles = "role_id_1,role_id_2",  -- Role IDs for admin access
    },
}

For Discord, add http://your-server-ip:port/admin/auth/callback to OAuth2 → Redirects in the Discord Developer Portal.

Set a strong password

The admin panel can change every server setting and install marketplace content. Never leave adminPassword at the default in production.

The tlib.admin ACE grants in-game features only (SGN button, trunked controls, 3D Prop Editor) — it does not unlock the web admin panel.

Pages

PageWhat you do here
OverviewLive stats: connected users, active channels, alerts, patches
Server SettingsAudio, FX, bonking, 3D, GPS, callsign, and misc settings — four tabs: General, Audio & Radio, Controls, Advanced (live, no restart)
Zones & ChannelsCreate, edit, and reorder zones and channels
CodeplugsDefine radio personality templates, allowed models, default layouts, channel access, and job mapping
AlertsAdd and edit alert definitions
SoundsMap tone names to WAV files (global and per-model) and upload custom sounds
MarketplaceBrowse, install, and manage layouts, sound packs, and themes

After first run, Zones & Channels is the live server config — editing Config.zones in config.lua has no effect while data.json is present. Codeplug changes apply on a player's next radio open; JOB_CODEPLUG_MAP changes require a resource restart.


Dispatch Panel

Dispatch Panel

Served at http://your-server-ip:port/. Accessible from a web browser or the native desktop app.

Desktop App

Download and install

Open http://your-server-ip:port/SettingsDownload under Desktop App. Run the installer. Or navigate directly to http://your-server-ip:port/radio/dispatch/installer.

Configure the endpoint

On first launch, enter your server URL and click Save.

Login

Enter your dispatchNacId from config.lua. If Discord auth is enabled, click Login with Discord instead.

The app auto-updates when the server version changes — an Update button appears in the header when a newer version is available.

Web Browser

Browsers block microphone access over HTTP. To enable voice:

MethodUse case
Chrome flag chrome://flags/#unsafely-treat-insecure-origin-as-secureDevelopment / local testing only
HTTPS reverse proxy (Nginx/Apache)Production

Without one of these, the panel shows a Mic denied warning.

Discord Authentication

Add this redirect URI under OAuth2 → Redirects in the Discord Developer Portal:

http://your-server-url:port/radio/dispatch/auth
config/config.lua
Config = {
    discord = {
        authEnabled  = true,
        clientId     = "YOUR_CLIENT_ID",
        clientSecret = "YOUR_CLIENT_SECRET",
        guildId      = "YOUR_GUILD_ID",
        roles        = "",  -- Comma-separated role IDs, or "" for all guild members
    },
}

Sessions last 24 hours. Role changes take effect on the next login.

Panel Features

Voice transmission — hold T (default) or the PTT button. Same P25/analog FX processing as in-game radios. PTT key is configurable per-channel in Settings; the desktop app accepts any key or mouse button globally.

Unit management:

  • Drag users between channels to reassign them
  • Click the hamburger menu on a user to disconnect, send a direct alert, or set their callsign

Frequency patching — bridge two or more channels so all patched units hear each other. Create from the channel menu. Trunked channels cannot be patched. A P badge shows on all patched channels. Patches persist across restarts.

Frequency patching

Announcement groups — arm a group of channels so your next PTT fans out to all of them simultaneously. Groups are defined in admin panel → Announcement Groups. When at least one group exists, a GROUPS bar appears above the zone list. Click a group to arm/disarm; PTT while armed broadcasts to every frequency in the group. Per-group PTT keybinds are configurable in Settings.

Transmission log — running log of all transmissions with timestamps, frequencies, and callers. Filterable, collapsible, and pop-outable (desktop only).


Alerts & Tones

Alert Lifecycle

Manage alerts in admin panel → Alerts. When triggered, a colored banner with the alert name appears on every connected radio. On first run, alerts are seeded from Config.alerts in config.lua.

Active alert banner on the radio display

Three phases, each with an optional tone:

  1. Activate — fires once on trigger; tones.activate plays
  2. Repeat — persistent alerts only; tones.repeat fires every repeatInterval ms
  3. Deactivate — fires once when cleared; tones.deactivate plays

Simple tone (same for all phases):

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

Per-phase tones:

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

Alert Field Reference

FieldTypeDefaultDescription
namestringDisplay text on the radio
colorstringHex color 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 cycle
tones.deactivatestringTone played once on clearance
repeatIntervalnumber (ms)random 5–10 sGap between repeat fires
repeatShowBannerbooltrueFlash banner on each repeat
deactivateLabelstring"RESUME"Banner text shown on clearance
deactivateColorstring"#126300"Color of the clearance banner
toneOnlyboolfalsePlay tone only, no visual banner

Triggering Alerts

MethodHow
In-game SGN buttonFirst alert in the list on the connected channel (dispatchers / tlib.admin)
Dispatch panelAny alert on any channel
Exportexports['tRadio']:setAlertOnChannel(freq, true, "Signal 100")
HTTPPOST /radio/dispatch/alert/trigger

Tone Catalog

Map tones to WAV files in admin panel → Sounds. Changes broadcast instantly, no restart required.

PTT tones (heard by the person transmitting):

ToneWhen played
PTTKey up — conventional channel
PTT_TRUNKEDKey up — trunked channel
PTT_PATCHEDKey up — patched channel
PTT_ENDKey release — conventional
PTT_END_TRUNKEDKey release — trunked
PTT_END_PATCHEDKey release — patched

TX tones (heard by receivers):

ToneWhen played
TX_STARTAnother user keys up — conventional
TX_START_TRUNKEDAnother user keys up — trunked
TX_START_PATCHEDAnother user keys up — patched
TX_ENDAnother user releases — conventional
TX_END_TRUNKEDAnother user releases — trunked
TX_END_PATCHEDAnother user releases — patched

General-purpose tones:

ToneTypical use
BEEPGeneric alert beep
BONKTransmission collision
CHIRPShort acknowledgment
ECHOMessage received
PANICPanic button activation
ALERT_A / ALERT_B / ALERT_CDefault alert-tone slots
PRIORITYDefault activate tone for priority alerts
PRIORITY_REPEATRepeat tone for persistent priority alerts

Radio Layouts

Ten models ship by default. The radio selects one automatically based on the player's current context:

ContextDefault model
On foot (focused)ATX-8000
On foot (unfocused, optional)ATX-8000H
In vehicleAFX-1500
In boatAFX-1500G
In aircraftTXDF-9100

ATX-8000H is only active when a player enables Separate On-Foot Focus Layout in /tradio.

All built-in models (use the folder name when specifying a model in codeplugs or the admin panel):

ModelForm factor
ATX-8000Portable handheld — default on-foot
ATX-8000HPortable handheld — on-foot unfocused variant
ATX-8000GPortable handheld — alternate body style
ATX-NOVAPortable handheld — modern slim design
AFX-1500Dashboard/vehicle-mounted — default in-vehicle
AFX-1500GDashboard/vehicle-mounted — boat variant
ARX-4000XPortable handheld — compact body
TXDF-9100Aviation radio — default in-aircraft
XPR-6500Portable handheld — XPR line
XPR-6500SPortable handheld — XPR line, slim variant

Set server-wide context defaults in admin panel → Layouts. Set per-vehicle defaults from inside a vehicle via /tradioSet as Default Layout for Vehicle — saves the active model for that spawn code and broadcasts to all clients. Install additional layouts from admin panel → MarketplaceLayouts; installed layouts appear immediately in every player's style picker.


Sound Customization

Per-Model Overrides

To override tones for one radio model only, place WAV files at:

layouts/{YOUR-MODEL}/sounds/{TONE_NAME}.wav

These take precedence over the global layouts/sounds/ set for any player using that model.

Background Transmission Sounds

These fixed-filename WAV files play during received transmissions. Place them in layouts/sounds/ — filenames are fixed and cannot be changed.

FilePurpose
transStart.wavClick-in at the start of a received transmission
transMid.wavLooping background noise during the transmission
transEnd.wavClick-out at the end (one of three picked randomly)
transEnd1.wavClick-out variant 2
transEnd2.wavClick-out variant 3
bgSiren.wavSiren loop (when bgModeCheck returns "siren")
bgHeli.wavHelicopter rotor loop
bgDog.wavK9 audio
bgShot.wavGunshot fallback sample

See Configuration → bgModeCheck to control which background plays based on in-game state (ELS, helicopter, K9, etc.).


Animations

How Animations Work

tRadio drives animations through two callbacks on each Config.animations entry:

CallbackWhen it firesTypical use
onKeyState(true/false)PTT key pressed / releasedActive transmitting pose
onRadioFocus(true/false)Radio UI opened / closedIdle holding pose

Both callbacks are optional — an empty function skips it. Players select their preferred animation from the radio settings menu. Index [1] is the fallback if a player's saved choice is no longer in the list.

Animations are automatically suppressed while the player is in the driver/pilot seat or holding a weapon. Always check ShouldSuppressAnim before starting any animation.

Built-in Animations

Four animations ship by default, all using native GTA V dictionaries (no extra resources required):

#NamePTT (onKeyState)Focus (onRadioFocus)
1None
2Shoulderrandom@arrestsgeneric_radio_entercellphone@cellphone_call_to_text + hand radio prop
3Handheldcellphone@cellphone_call_to_text + hand radio propcellphone@cellphone_call_to_text + hand radio prop
4Earpiececellphone@cellphone_call_listen_base

MakeAnimHandler is a helper that loads the animation dictionary asynchronously and cleans up on release:

config/cl_functions.lua
[2] = {
    name         = "Shoulder",
    onKeyState   = MakeAnimHandler("random@arrests", "generic_radio_enter", nil),
    onRadioFocus = MakeAnimHandler("cellphone@", "cellphone_call_to_text", "prop_cs_hand_radio"),
},

Adding Custom Animations with rpemotes

Servers running rpemotes can add more realistic poses by wiring tRadio into rpemotes emotes. Each custom animation needs:

  1. An rpemotes emote that plays the animation
  2. A new entry in Config.animations that calls it

Using an existing rpemotes emote (e.g. wt, wt4):

config/cl_functions.lua
[5] = {
    name = "Handheld (rpemotes)",
    onKeyState = function(isKeyDown)
        local playerPed = PlayerPedId()
        if isKeyDown then
            if ShouldSuppressAnim(playerPed) then return end
            exports["rpemotes"]:EmoteCommandStart("wt4", 0)
        else
            exports["rpemotes"]:EmoteCancel(true)
        end
    end,
    onRadioFocus = function(focused)
        local playerPed = PlayerPedId()
        if focused then
            if ShouldSuppressAnim(playerPed) then return end
            exports["rpemotes"]:EmoteCommandStart("wt", 0)
        else
            exports["rpemotes"]:EmoteCancel(true)
        end
    end,
},

Using Custom .ycd Animation Files

Third-party animation packs ship as .ycd files. To use them in tRadio:

Step 1 — Stream the files through rpemotes. Copy the .ycd files into a subfolder under rpemotes/stream/:

rpemotes/
  stream/
    [Custom Emotes]/
      Radio/
        anim@cop_mic_pose_002.ycd

Step 2 — Find the clip name. The .ycd filename is the dictionary name. Open it in OpenIV or CodeWalker to inspect the clip list, or test in-game:

RequestAnimDict("anim@cop_mic_pose_002")
while not HasAnimDictLoaded("anim@cop_mic_pose_002") do Wait(10) end
TaskPlayAnim(PlayerPedId(), "anim@cop_mic_pose_002", "chest_mic", 8.0, 2.0, -1, 1, 0, false, false, false)

Step 3 — Register the emote in rpemotes. Add to rpemotes/client/AnimationListCustom.lua (never edit AnimationList.lua directly):

rpemotes/client/AnimationListCustom.lua
CustomDP.Emotes = {
    ["radiochest"] = {
        "anim@cop_mic_pose_002",  -- dictionary name (filename without .ycd)
        "chest_mic",              -- clip name inside the dictionary
        "Radio Chest",            -- display name in the rpemotes emote menu
        AnimationOptions = { onFootFlag = AnimFlag.MOVING },
    },
}

Step 4 — Add the entry to tRadio:

config/cl_functions.lua
[5] = {
    name = "Chest Mic",
    onKeyState = function(isKeyDown)
        local playerPed = PlayerPedId()
        if isKeyDown then
            if ShouldSuppressAnim(playerPed) then return end
            exports["rpemotes"]:EmoteCommandStart("radiochest", 0)
        else
            exports["rpemotes"]:EmoteCancel(true)
        end
    end,
    onRadioFocus = function(focused)
        local playerPed = PlayerPedId()
        if focused then
            if ShouldSuppressAnim(playerPed) then return end
            exports["rpemotes"]:EmoteCommandStart("wt", 0)
        else
            exports["rpemotes"]:EmoteCancel(true)
        end
    end,
},

Restart rpemotes, then tRadio. The new entry appears in the radio settings menu.

Step 5 — (Optional) rpemotes AnimationOptions fields:

FieldDescription
onFootFlag = AnimFlag.MOVINGLoop the animation and allow slight movement blending
onFootFlag = AnimFlag.LOOPLoop without movement blending
PropProp model name to attach
PropBoneBone index (28422 = right hand, 60309 = left hand)

Animation Reference

MakeAnimHandler(dict, anim, prop)

ArgumentTypeDescription
dictstringGTA V animation dictionary (e.g. "cellphone@")
animstringClip name within that dictionary
propstring|nilProp model to attach to the right hand, or nil for none

Find prop model names at forge.plebmasters.de/objects.

ShouldSuppressAnim(ped) — returns true when the player is in the driver/pilot seat or holding a weapon.

rpemotes exports:

exports["rpemotes"]:EmoteCommandStart("emoteName", 0)  -- start emote
exports["rpemotes"]:EmoteCancel(true)                  -- cancel (true = play cancel anim)

Dispatch Panel Themes

Dispatch themes are published on the marketplace and installed from admin panel → MarketplaceThemes. A theme can be a simple colour palette swap or a completely custom dispatch UI built in JavaScript.

Each theme has two optional layers:

LayerFieldWhat it does
CSScssTextOverride CSS variables and inject raw styles
JavaScriptjsTextFull programmatic access — read state, react to events, call actions, replace the DOM

CSS-only themes are always safe. JS themes require an explicit source-review acceptance before installing.

To create a theme: go to tgstudios → Marketplace → Themes → Create Theme, write your CSS (required) and optionally JavaScript, and publish. Updates to a published theme are available to installers on their next dispatch panel refresh.

CSS Variables

Override any of these in your theme's cssText:

VariableDefault (Dark Minimal)Purpose
--bg-primary#030303Page background
--bg-secondary#141414Channel cards, panels
--bg-hover#262626Hover / subtle highlight
--text-primary#fafafaPrimary labels and callsigns
--text-secondary#a3a3a3Frequencies, counts, hints
--accent-raw#4da6ffActive channel, links, PTT indicator
--danger#ef4444Panic / emergency
--success#22c55eOnline / connected
--my-dispatch-bghsl(220 100% 13%)Your joined channel — background
--my-dispatch-borderhsl(220 100% 28%)Your joined channel — border
--other-dispatch-bghsl(270 100% 13%)Other dispatchers' channel — background
--other-dispatch-borderhsl(270 100% 28%)Other dispatchers' channel — border

Shadcn/UI variables (also overridable):

VariablePurpose
--background / --foregroundRoot page background / text
--card / --card-foregroundCard surfaces
--primary / --primary-foregroundPrimary interactive elements
--secondary / --secondary-foregroundSecondary surfaces
--muted / --muted-foregroundDe-emphasised areas
--destructive / --destructive-foregroundDanger / delete actions
--borderComponent borders
--inputInput field background
--ringFocus ring colour
--radiusGlobal border radius

Example — dark green theme:

--bg-primary:     #0a0f0a;
--bg-secondary:   #111a11;
--bg-hover:       #1e2e1e;
--accent-raw:     #4caf50;
--text-primary:   #e8f5e9;
--text-secondary: #a5d6a7;
--my-dispatch-bg:     hsl(120 60% 10%);
--my-dispatch-border: hsl(120 60% 25%);

Set extends to a built-in theme ID to inherit all its variables and only override what you need. Available base IDs: dark-minimal (default), dark, dark-blue, basic, basic-dark.

These five built-in themes are also directly selectable in the dispatch panel's own Settings menu — no marketplace install required.


Dispatch JavaScript API

The full JS API reference — globals (dispatch, theme, onUnmount), events, actions, DOM control, and a complete panel replacement example — is documented on the API Reference page.

JS themes run directly in the dispatch panel with full DOM access. Dispatchers are shown the complete source code and must explicitly accept a warning before installation.

Theme JavaScript receives three injected globals:

GlobalPurpose
dispatchRead state, subscribe to events, call actions
themeCSS helpers, DOM control
onUnmount(fn)Register cleanup for when the theme changes or unmounts

See the API Reference for the full reference: dispatch.getState() state shape, all events, all actions, DOM control methods, security model, and a complete panel replacement example.

On this page

Need help?

Ask on Discord