Configuration
Overview
config.lua: Every gameplay setting: items, signal model, antennas, jammers, stations, zones, stereos, channels, UI.server/config_server.lua: Server-only: logging provider, Discord webhooks, export allowlist, stream host allowlist.
Stations, speaker zones and fixed towers are defined in config.lua and load on resource start; changing them requires a resource restart.
Core
config.lua
1Config.Debug = false
2Config.Locale = 'en'
3Config.Mode = 'item' -- 'item' | 'command'
4Config.disconnectOnDeath = true
5Config.MaxFrequency = 999.99Debug: Development prints. Leavefalsein production.Locale: Interface language. Translations live in thelocales/folder.Mode:'item'requires a radio item in the inventory;'command'opens the radio without an item.disconnectOnDeath: Drops the player off the air when they die.MaxFrequency: Highest tunable frequency.
Radio Items
config.lua
1Config.Items.radios = {
2 { name = 'radio', label = 'Basic Radio', rangeBonus = 0, batteryMax = 100, batteryDrain = 0.5 },
3 { name = 'radio_military', label = 'Military Radio', rangeBonus = 2000, batteryMax = 150, batteryDrain = 0.3 },
4}name: Inventory item name.rangeBonus: Meters added on top of the signal coverage for this handset.batteryMax: Battery capacity in percent points.batteryDrain: Percent drained per battery tick while the radio is on and tuned.
config.lua
1Config.Items.consumables = {
2 battery = 'batteria_stilo',
3 repairKit = 'electronic_kit'
4}battery: Item consumed to recharge a handset.repairKit: Default repair item shown in interaction menus.
Jammer Items
config.lua
1Config.Items.jammers = {
2 { name = 'radio_jammer', label = 'Basic Jammer', model = 'sm_prop_smug_jammer',
3 rangeMin = 10.0, rangeMax = 100.0, rangeDefault = 50.0,
4 powerMax = 100, powerConsumption = 10, healthMax = 100, maxPerPlayer = 2 },
5 { name = 'radio_jammer_advanced', label = 'Advanced Jammer', model = 'sm_prop_smug_jammer',
6 rangeMin = 10.0, rangeMax = 200.0, rangeDefault = 100.0,
7 powerMax = 150, powerConsumption = 8, healthMax = 150, maxPerPlayer = 1 },
8}rangeMin/rangeMax/rangeDefault: Owner-adjustable jamming radius bounds and its starting value.powerMax/powerConsumption: Power reserve and how much drains per tick while active. Batteries refill it.healthMax: Durability. Jammers can be damaged and destroyed whenConfig.Jammer.durability.destroyableis on.maxPerPlayer: Placement cap per player for this tier.
Antenna Items
config.lua
1Config.Items.antennas = {
2 { name = 'radio_antenna_small', label = 'Portable Antenna', model = 'dgm_antenna',
3 signalRange = 500.0, healthMax = 100, weatherResistance = 0, maxPerPlayer = 3,
4 repairItems = { ['electronic_kit'] = { addHealth = 25, duration = 5000 } },
5 dui = { enabled = true, renderDistance = 15.0, scale = 0.08, offset = vec3(0.0, 0.0, 0.5) } },
6 -- radio_antenna_medium: 1500m, 200hp, 40 weather resistance, max 2
7 -- radio_antenna_large: 3000m, 300hp, 80 weather resistance, max 1
8}signalRange: Coverage radius projected by the antenna.weatherResistance: Percent of the rain/thunder signal penalty this tier ignores.repairItems: Items accepted for repairs, each with health restored and progress duration.dui: The in-world status display (health, range, lockout) rendered above the prop: toggle, view distance, scale, offset.
Antenna Model
The default model = 'dgm_antenna' must exist on your server. If you do not stream that model, point model (on antenna tiers and every fixed location) at a base-game prop such as prop_radiomast01.
Signal
config.lua
1Config.Signal = {
2 enabled = true,
3 baseSignalWithoutAntenna = 0,
4 baseRange = 5000.0,
5 updateInterval = 5000,
6 degradation = {
7 distance = 0.02,
8 buildings = 15,
9 underground = 50,
10 weather = { rain = 10, thunder = 25 }
11 },
12 thresholds = { excellent = 80, good = 60, poor = 40, weak = 20 }
13}baseSignalWithoutAntenna: Signal level with no antenna in range.0means no coverage without infrastructure.baseRange: Fallback coverage radius when signal simulation applies without a specific antenna.updateInterval: Milliseconds between signal recalculations per client.degradation.distance: Signal points lost per meter from the antenna, scaled by range.degradation.buildings/underground: Flat penalty while indoors / below ground.degradation.weather: Flat penalty during rain and thunder, reduced by the antenna'sweatherResistance.thresholds: Quality tier boundaries shown on the handset.
Placed Antennas
config.lua
1Config.Antenna = {
2 enabled = true,
3 maxPerPlayer = 5,
4 spawnDistance = 1.0,
5 interactionDistance = 3.0,
6 saveHealthToItem = true,
7 decay = { enabled = false, rate = 1, tickInterval = 60000 },
8 hack = {
9 enabled = true,
10 defaultItem = 'hacking_device',
11 duration = 15000,
12 successChance = 0.7,
13 damageOnSuccess = 50,
14 removeItemOnFail = false
15 },
16 permission = { place = {}, configure = {}, remove = {} }
17}maxPerPlayer: Global cap across all antenna tiers, on top of each tier's ownmaxPerPlayer.saveHealthToItem: Writes remaining health onto the item when an antenna is picked up, restored on re-placement.decay: Optional passive health loss per tick. Off by default.hack.defaultItem: Item required to attempt a hack. Individual tiers can override withhackItem,hackDuration,hackSuccessChance,hackLockDurationandhackDamageOnSuccesson the tier entry.hack.duration: Hack attempt length in milliseconds.hack.successChance: Roll from 0 to 1. Failures keep the item unlessremoveItemOnFail = true.hack.damageOnSuccess: Health chipped off the antenna on a successful hack, on top of the lockout. Infinite-health towers are immune. Set0for lockout only.permission: Job allowlists per action. Empty tables allow everyone.
Fixed Towers
config.lua
1Config.FixedAntennas = {
2 enabled = true,
3 forceOnline = true,
4 locations = {
5 { id = 'shores', label = 'Shores Tower', model = 'dgm_antenna',
6 coords = vec3(2296.64, 2954.15, 45.54), heading = 91.1,
7 signalRange = 3500.0, infiniteHealth = true },
8 -- villa, vinewood, southside, chilliad ship pre-configured
9 }
10}forceOnline: Fixed towers come back online automatically on start.infiniteHealth: The tower never breaks and ignores hack damage.hackable = true: Per-location opt-in to allow hacking a fixed tower. Off unless set.
Jammers
config.lua
1Config.Jammer = {
2 enabled = true,
3 permission = { place = {}, configure = {}, remove = {} },
4 defaultJammers = {},
5 durability = { enabled = true, decayRate = 2, destroyable = true }
6}defaultJammers: Pre-placed jammers defined in config.durability.decayRate: Health lost per tick while the jammer runs.durability.destroyable: Allows the prop to be damaged and destroyed by players.
Broadcast Stations
config.lua
1Config.Broadcast = {
2 enabled = true,
3 streamingProvider = 'xsound', -- 'xsound' | 'none'
4 stations = {
5 { id = 'police_dispatch', label = 'LSPD Dispatch',
6 coords = vec3(392.44, -832.0, 29.29), frequency = 1.0,
7 permission = { 'police' }, speakerRange = 0, allowStreaming = true },
8 -- hospital_pa (freq 2.0, ambulance), radio_station (88.4, dj/media + autoplay)
9 },
10 ducking = { enabled = true, voiceVolume = 0.1 }
11}streamingProvider:'xsound'streams real audio;'none'disables music while keeping the voice side of stations.permission: Jobs allowed to operate the station console.speakerRange: Meters around the station prop where audio is heard without a radio.0disables the local speaker.allowStreaming: Whether the console accepts stream links.ducking.voiceVolume: Music volume multiplier while someone talks on the station frequency.
Autoplay
config.lua
1autoplay = {
2 enabled = true,
3 shuffle = false,
4 defaultDuration = 1800,
5 tracks = {
6 { url = 'https://ice1.somafm.com/groovesalad-128-mp3', title = 'Groove Salad' },
7 }
8}- Per-station 24/7 playlist that keeps speaker zones and boomboxes on that frequency audible with no live DJ. A live DJ takes priority; autoplay resumes when the console is released.
defaultDuration: Seconds per track before rotating when the stream has no track metadata.- Replace the shipped example streams with your own links.
Speaker Zones
config.lua
1Config.SpeakerZones = {
2 enabled = true,
3 zones = {
4 { id = 'prison_yard', label = 'Bolingbroke Yard Speakers',
5 coords = vec3(285.59, -901.16, 28.81), radius = 50.0,
6 frequency = 1.0, volume = 0.5 },
7 }
8}- Anyone inside the radius hears whatever plays on the zone's
frequency, no radio item needed. Volume falls off toward the edge.
Placeable Stereos
config.lua
1Config.StereoProps = {
2 enabled = true,
3 items = {
4 { name = 'boombox', label = 'Boombox', model = 'prop_ghettoblast_01',
5 maxDistance = 25.0, volumeDefault = 0.6, maxPerPlayer = 10 },
6 },
7 interaction = { distance = 2.0, icon = 'fa-radio' },
8 presets = { { label = 'Radio Los Santos', freq = 88.4 } },
9 publicControl = false,
10 battery = { enabled = false, startLevel = 100, rechargeItem = 'batteria_stilo',
11 rechargeAmount = 50, drainPerMinute = 0.5 }
12}presets: Quick-pick channels in the stereo menu; free tuning stays available.publicControl:falserestricts power, tuning and volume to the owner.battery: Optional drainable battery for placed stereos. Off by default.
Handset Battery
config.lua
1Config.Battery = {
2 enabled = true,
3 tickInterval = 60000,
4 drainPerMinute = 0.5,
5 chargeAmount = 50,
6 lowThreshold = 20
7}tickInterval: Milliseconds between server-side drain passes.chargeAmount: Percent restored per battery item consumed.lowThreshold: Percentage that fires the low-battery warning once.
Channels
config.lua
1Config.Channels = {
2 names = {
3 ['1'] = 'LSPD CH#1',
4 ['1.%'] = 'LSPD Private',
5 ['2'] = 'EMS Dispatch',
6 ['2.%'] = 'EMS Private',
7 ['88.4'] = 'Radio Los Santos'
8 },
9 restrictions = {
10 [1] = { type = 'job', names = { 'police', 'lspd' }, requireDuty = true },
11 [2] = { type = 'job', names = { 'ambulance', 'ems' }, requireDuty = true }
12 }
13}names: Display labels. The.%pattern names every sub-channel of a band (1.1,1.2, ...).restrictions: Job locks per whole-number frequency.requireDutyalso checks on-duty state. Players without the job hear the channel as listen-only.
Interface
config.lua
1Config.UI = {
2 keybind = 'F5',
3 overlay = 'default',
4 theme = 'military',
5 allowMovement = true,
6 channelUpKey = 'PERIOD',
7 channelDownKey = 'COMMA',
8 scaleFactor = 0.85,
9 defaultPosition = 'bottom-right',
10 enableMicClicks = true,
11 enablePlayerMute = true
12}keybind: Opens the radio. Players can rebind it in FiveM settings.theme: Interface skin. Shipped themes live in theweb/build.allowMovement: Lets the player walk while the radio is open.scaleFactor/defaultPosition: Starting size and placement; players can drag and resize, saved per player./resetradiouirestores defaults.enableMicClicks: Push-to-talk click sounds.enablePlayerMute: Per-player mute entries in the channel member list.
Server Config
server/config_server.lua
1Config.Logger = 'discord' -- 'discord' | 'ox_lib'
2
3Config.DiscordWebhook = {
4 ChannelJoin = 'INSERT_WEBHOOK_LINK_HERE',
5 ChannelLeave = 'INSERT_WEBHOOK_LINK_HERE',
6 JammerPlaced = 'INSERT_WEBHOOK_LINK_HERE',
7 ExtenderPlaced = 'INSERT_WEBHOOK_LINK_HERE',
8 BroadcastStart = 'INSERT_WEBHOOK_LINK_HERE',
9 ExploitDetected = 'INSERT_WEBHOOK_LINK_HERE',
10 HackSuccess = 'INSERT_WEBHOOK_LINK_HERE',
11}
12
13Config.AllowedExportResources = {}
14Config.AllowedStreamHosts = {}Logger: Where log entries go.DiscordWebhook: One webhook per event category. Set any webhook tofalseto disable that log entirely.AllowedExportResources: Non-ml_*resources allowed to call mutating exports.ml_*resources are allowed automatically.AllowedStreamHosts: Restrict DJ stream links to specific hosts. Empty allows any public host; private addresses are always blocked.