Files
Elite-Gaming-FiveM/resources/radio/config.lua
T
2025-12-07 16:20:05 -08:00

754 lines
32 KiB
Lua

-- Tommy's Radio System Configuration
-- Documentation can be found at: https://tommys-scripts.gitbook.io/fivem/paid-scripts/tommys-radio/setup-and-configuration
-- Config Version 2.9 - Use a website like https://www.diffchecker.com/ to compare configuration file changes
Config = {
-- Available radio layouts
radioLayouts = {
"AFX-1500",
"AFX-1500G",
"ARX-4000X",
"XPR-6500",
"XPR-6500S",
"ATX-8000",
"ATX-8000G",
"ATX-NOVA",
"TXDF-9100",
},
-- Default layouts by vehicle type
defaultLayouts = {
["Handheld"] = "ATX-8000",
["Vehicle"] = "AFX-1500",
["Boat"] = "AFX-1500G",
["Air"] = "TXDF-9100",
-- Example default layouts by spawn code
["fbi2"] = "XPR-6500",
["police"] = "XPR-6500",
},
-- Control keys
controls = {
talkRadioKey = "B",
toggleRadioKey = "I",
channelUpKey = "",
channelDownKey = "",
zoneUpKey = "",
zoneDownKey = "",
menuUpKey = "",
menuDownKey = "",
menuRightKey = "",
menuLeftKey = "",
menuHomeKey = "",
menuBtn1Key = "",
menuBtn2Key = "",
menuBtn3Key = "",
emergencyBtnKey = "",
closeRadioKey = "",
powerBtnKey = "",
styleUpKey= "",
styleDownKey= "",
voiceVolumeUpKey = "",
voiceVolumeDownKey = "",
sfxVolumeUpKey = "",
sfxVolumeDownKey = "",
volume3DUpKey = "",
volume3DDownKey = "",
},
-- Network settings
connectionAddr = "93.23.161.198/32", -- Final connection string for clients, Including protocol (https://proxy.example.com) and/or port. (If empty, uses host ip address and port)
serverPort = njb8l4, -- Port for radio server & dispatch panel, choose a port that is not used by other resources.
authToken = "EGRPLAW", -- 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. If an in-game player has this NAC ID, they can access the trunked control frequency & trigger SGN alerts.
-- General settings
doUpdateCheck = true, -- Enable automatic update checking on resource start
logLevel = 3, -- (0 = Error, 1 = Warnings, 2 = Minimal, 3 = Normal, 4 = Debug, 5 = Verbose)
pttReleaseDelay = 350, -- Delay in milliseconds before releasing PTT to prevent cut-off (250-500ms recommended)
panicTimeout = 60000,
-- Audio settings
voiceVolume = 65, -- Default voice volume (0-100), can be changed in radio settings menu
sfxVolume = 35, -- Default sfx volume (0-100), can be changed in radio settings menu
volumeStep = 5, -- Volume change increment when using volume up/down keys for all volume types (1-20 recommended, default: 5)
playTransmissionEffects = true, -- Play background sound effects (sirens, helis, gunshots)
analogTransmissionEffects = true, -- Play analog transmission sound effects (static during transmission)
-- 3D Audio settings (EXPERIMENTAL)
default3DAudio = true, -- true = earbuds OFF by default (3D audio enabled), false = earbuds ON by default (3D audio disabled)
default3DVolume = 50, -- Default 3D audio volume (0-100), saved per user like voice/sfx volume, default is 50
vehicle3DActivationDistance = 5.0, -- Minimum distance (in meters) the owner must be from their vehicle for 3D audio to activate from that vehicle
-- Signal Tower Coordinates (for signal strength calculation) - DOES NOT affect voice quality currently. Used for signal icon display.
signalTowerCoordinates = {
{ x = 1860.0, y = 3677.0, z = 33.0 },
{ x = 449.0, y = -992.0, z = 30.0 },
{ x = -979.0, y = -2632.0, z = 23.0 },
{ x = -2364.0, y = 3229.0, z = 45.0 },
{ x = -449.0, y = 6025.0, z = 35.0 },
{ x = 1529.0, y = 820.0, z = 79.0 },
{ x = -573.0, y = -146.0, z = 38.0 },
{ x = -3123.0, y = 1334.0, z = 25.0 },
{ x = 5266.79, y = -5427.7, z = 139.7 },
},
-- Battery system configuration
-- This function is called every second to update the battery level
-- @param currentBattery: number - current battery level (0-100)
-- @param deltaTime: number - time since last update in seconds
-- @return: number - new battery level (0-100)
batteryTick = function(currentBattery, deltaTime)
local playerPed = PlayerPedId()
local vehicle = GetVehiclePedIsIn(playerPed, false)
if vehicle ~= 0 then
-- Charge battery when in vehicle
local chargeRate = 0.5 -- 0.5% per second
return math.min(100.0, currentBattery + (chargeRate * deltaTime))
else
-- Discharge battery when on foot
local dischargeRate = 0.1 -- 0.5% per second
return math.max(0.0, currentBattery - (dischargeRate * deltaTime))
end
end,
-- Multiple Animation Configurations
-- Users can select which animation to use through radio settings (you can remove or add more options)
animations = {
[1] = {
name = "None",
onKeyState = function(isKeyDown)
-- Empty function for no animations
end,
onRadioFocus = function(focused)
-- Empty function for no animations
end
},
[2] = {
name = "Shoulder",
onKeyState = function(isKeyDown)
local playerPed = PlayerPedId()
if not playerPed or playerPed == 0 then return end
-- Initialize animation state tracker if it doesn't exist
if not _radioAnimState then
_radioAnimState = {
isPlaying = false,
pendingStart = false,
dictLoaded = false
}
end
if isKeyDown then
-- Mark that we want to start animation
_radioAnimState.pendingStart = true
-- Animation when starting to talk (key down)
RequestAnimDict('random@arrests')
-- Non-blocking check for animation dictionary
Citizen.CreateThread(function()
local attempts = 0
while not HasAnimDictLoaded("random@arrests") and attempts < 50 do
Citizen.Wait(10)
attempts = attempts + 1
end
-- Only start animation if we still want to start it (user hasn't released PTT)
if _radioAnimState.pendingStart and HasAnimDictLoaded("random@arrests") then
_radioAnimState.dictLoaded = true
if not IsEntityPlayingAnim(playerPed, "random@arrests", "generic_radio_enter", 3) then
TaskPlayAnim(playerPed, "random@arrests", "generic_radio_enter", 8.0, 2.0, -1, 50, 2.0, false,
false, false)
_radioAnimState.isPlaying = true
end
end
end)
else
-- Animation when stopping talk (key up)
_radioAnimState.pendingStart = false
-- Stop animation immediately regardless of loading state
if _radioAnimState.isPlaying or IsEntityPlayingAnim(playerPed, "random@arrests", "generic_radio_enter", 3) then
StopAnimTask(playerPed, "random@arrests", "generic_radio_enter", -4.0)
_radioAnimState.isPlaying = false
end
end
end,
onRadioFocus = function(focused)
local playerPed = PlayerPedId()
if not playerPed or playerPed == 0 then return end
-- Initialize animation state tracker if it doesn't exist
if not _radioAnimState then
_radioAnimState = {
isPlaying = false,
pendingStart = false,
dictLoaded = false,
radioProp = nil
}
end
if focused then
-- Start handheld radio animation when focused
if not _radioAnimState.isPlaying then
RequestAnimDict('cellphone@')
Citizen.CreateThread(function()
local attempts = 0
while not HasAnimDictLoaded("cellphone@") and attempts < 50 do
Citizen.Wait(10)
attempts = attempts + 1
end
if HasAnimDictLoaded("cellphone@") then
if not IsEntityPlayingAnim(playerPed, "cellphone@", "cellphone_call_to_text", 3) then
TaskPlayAnim(playerPed, "cellphone@", "cellphone_call_to_text", 8.0, 2.0, -1, 50, 2.0, false, false, false)
-- Create and attach radio prop
_radioAnimState.radioProp = CreateObject(GetHashKey("prop_cs_hand_radio"), 0, 0, 0, true, true, true)
AttachEntityToEntity(_radioAnimState.radioProp, playerPed, GetPedBoneIndex(playerPed, 28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, true, false, true, 1, true)
SetEntityAsMissionEntity(_radioAnimState.radioProp, true, true)
_radioAnimState.isPlaying = true
end
end
end)
end
else
-- Stop handheld radio animation when losing focus
if _radioAnimState.isPlaying then
if IsEntityPlayingAnim(playerPed, "cellphone@", "cellphone_call_to_text", 3) then
StopAnimTask(playerPed, "cellphone@", "cellphone_call_to_text", -4.0)
end
-- Delete radio prop
if _radioAnimState.radioProp then
DeleteObject(_radioAnimState.radioProp)
_radioAnimState.radioProp = nil
end
_radioAnimState.isPlaying = false
end
end
end
},
[3] = {
name = "Handheld",
onKeyState = function(isKeyDown)
local playerPed = PlayerPedId()
if not playerPed or playerPed == 0 then return end
if not _radioAnimState then
_radioAnimState = {
isPlaying = false,
pendingStart = false,
dictLoaded = false,
radioProp = nil
}
end
if isKeyDown then
_radioAnimState.pendingStart = true
RequestAnimDict('cellphone@')
Citizen.CreateThread(function()
local attempts = 0
while not HasAnimDictLoaded("cellphone@") and attempts < 50 do
Citizen.Wait(10)
attempts = attempts + 1
end
if _radioAnimState.pendingStart and HasAnimDictLoaded("cellphone@") then
_radioAnimState.dictLoaded = true
if not IsEntityPlayingAnim(playerPed, "cellphone@", "cellphone_call_to_text", 3) then
TaskPlayAnim(playerPed, "cellphone@", "cellphone_call_to_text", 8.0, 2.0, -1, 50, 2.0, false, false, false)
-- Create and attach radio prop
_radioAnimState.radioProp = CreateObject(GetHashKey("prop_cs_hand_radio"), 0, 0, 0, true, true, true)
AttachEntityToEntity(_radioAnimState.radioProp, playerPed, GetPedBoneIndex(playerPed, 28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, true, false, true, 1, true)
SetEntityAsMissionEntity(_radioAnimState.radioProp, true, true)
_radioAnimState.isPlaying = true
end
end
end)
else
_radioAnimState.pendingStart = false
if _radioAnimState.isPlaying or IsEntityPlayingAnim(playerPed, "cellphone@", "cellphone_call_to_text", 3) then
StopAnimTask(playerPed, "cellphone@", "cellphone_call_to_text", -4.0)
-- Delete radio prop
if _radioAnimState.radioProp then
DeleteObject(_radioAnimState.radioProp)
_radioAnimState.radioProp = nil
end
_radioAnimState.isPlaying = false
end
end
end,
onRadioFocus = function(focused)
local playerPed = PlayerPedId()
if not playerPed or playerPed == 0 then return end
-- Initialize animation state tracker if it doesn't exist
if not _radioAnimState then
_radioAnimState = {
isPlaying = false,
pendingStart = false,
dictLoaded = false,
radioProp = nil
}
end
if focused then
-- Start handheld radio animation when focused
if not _radioAnimState.isPlaying then
RequestAnimDict('cellphone@')
Citizen.CreateThread(function()
local attempts = 0
while not HasAnimDictLoaded("cellphone@") and attempts < 50 do
Citizen.Wait(10)
attempts = attempts + 1
end
if HasAnimDictLoaded("cellphone@") then
if not IsEntityPlayingAnim(playerPed, "cellphone@", "cellphone_call_to_text", 3) then
TaskPlayAnim(playerPed, "cellphone@", "cellphone_call_to_text", 8.0, 2.0, -1, 50, 2.0, false, false, false)
-- Create and attach radio prop
_radioAnimState.radioProp = CreateObject(GetHashKey("prop_cs_hand_radio"), 0, 0, 0, true, true, true)
AttachEntityToEntity(_radioAnimState.radioProp, playerPed, GetPedBoneIndex(playerPed, 28422), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, true, false, true, 1, true)
SetEntityAsMissionEntity(_radioAnimState.radioProp, true, true)
_radioAnimState.isPlaying = true
end
end
end)
end
else
-- Stop handheld radio animation when losing focus
if _radioAnimState.isPlaying then
if IsEntityPlayingAnim(playerPed, "cellphone@", "cellphone_call_to_text", 3) then
StopAnimTask(playerPed, "cellphone@", "cellphone_call_to_text", -4.0)
end
-- Delete radio prop
if _radioAnimState.radioProp then
DeleteObject(_radioAnimState.radioProp)
_radioAnimState.radioProp = nil
end
_radioAnimState.isPlaying = false
end
end
end
},
[4] = {
name = "Earpiece",
onKeyState = function(isKeyDown)
local playerPed = PlayerPedId()
if not playerPed or playerPed == 0 then return end
-- Initialize animation state tracker if it doesn't exist
if not _radioAnimState then
_radioAnimState = {
isPlaying = false,
pendingStart = false,
dictLoaded = false
}
end
if isKeyDown then
-- Mark that we want to start animation
_radioAnimState.pendingStart = true
-- Animation when starting to talk (key down)
RequestAnimDict('cellphone@')
-- Non-blocking check for animation dictionary
Citizen.CreateThread(function()
local attempts = 0
while not HasAnimDictLoaded("cellphone@") and attempts < 50 do
Citizen.Wait(10)
attempts = attempts + 1
end
-- Only start animation if we still want to start it (user hasn't released PTT)
if _radioAnimState.pendingStart and HasAnimDictLoaded("cellphone@") then
_radioAnimState.dictLoaded = true
if not IsEntityPlayingAnim(playerPed, "cellphone@", "cellphone_call_listen_base", 3) then
TaskPlayAnim(playerPed, "cellphone@", "cellphone_call_listen_base", 8.0, 2.0, -1, 50, 2.0, false,
false, false)
_radioAnimState.isPlaying = true
end
end
end)
else
-- Animation when stopping talk (key up)
_radioAnimState.pendingStart = false
-- Stop animation immediately regardless of loading state
if _radioAnimState.isPlaying or IsEntityPlayingAnim(playerPed, "cellphone@", "cellphone_call_listen_base", 3) then
StopAnimTask(playerPed, "cellphone@", "cellphone_call_listen_base", -4.0)
_radioAnimState.isPlaying = false
end
end
end,
onRadioFocus = function(focused)
-- No animation on focus/unfocus for Earpiece
-- Animation is handled by onKeyState (PTT)
end
},
--[[ Check the documentation for more details on how to add these animations.
[5] = {
name = "Chest",
onKeyState = function(isKeyDown)
-- Use radio chest animation from RP Emotes
if isKeyDown then
exports["rpemotes"]:EmoteCommandStart("radiochest", 0)
else
exports["rpemotes"]:EmoteCancel(true)
end
end,
onRadioFocus = function(focused)
if focused then
exports["rpemotes"]:EmoteCommandStart("wt", 0)
else
exports["rpemotes"]:EmoteCancel(true)
end
end
},
[6] = {
name = "Handheld2",
onKeyState = function(isKeyDown)
-- Use radio chest animation from RP Emotes
if isKeyDown then
exports["rpemotes"]:EmoteCommandStart("wt4", 0)
else
exports["rpemotes"]:EmoteCancel(true)
end
end,
onRadioFocus = function(focused)
if focused then
exports["rpemotes"]:EmoteCommandStart("wt", 0)
else
exports["rpemotes"]:EmoteCancel(true)
end
end
},
--]]
},
-- Interference settings
bonkingEnabled = true,
bonkInterval = 750,
interferenceTimeout = 5000,
blockAudioDuringInterference = true,
-- Permission check for radio access (SERVER ONLY)
radioAccessCheck = function(playerId)
if not playerId or playerId <= 0 then
Logger.error("Invalid playerId in radioAccessCheck: " .. tostring(playerId))
return false
end
-- QB-Core example (uncomment and modify as needed):
-- local success, player = pcall(function()
-- return exports['qb-core']:GetPlayer(playerId)
-- end)
--
-- if success and player and player.PlayerData and player.PlayerData.job then
-- local jobName = player.PlayerData.job.name
-- return jobName == "police" or jobName == "ambulance" or jobName == "firefighter" or jobName == "ems"
-- end
--
-- return false
return true -- Default: allow everyone
end,
-- Get user NAC ID (SERVER ONLY)
getUserNacId = function(serverId)
if not serverId or serverId <= 0 then
return nil
end
-- QB-Core example (uncomment and modify as needed):
-- local success, player = pcall(function()
-- return exports['qb-core']:GetPlayer(serverId)
-- end)
--
-- if success and player and player.PlayerData and player.PlayerData.job then
-- local jobName = player.PlayerData.job.name
-- if jobName == "police" then
-- return "141"
-- elseif jobName == "ambulance" or jobName == "ems" then
-- return "200"
-- elseif jobName == "firefighter" then
-- return "300"
-- end
-- end
--
-- return "0"
return "141"
end,
-- Get player display name (SERVER ONLY)
getPlayerName = function(serverId)
if not serverId then return "DISPATCH" end
if serverId <= 0 then return "DISPATCH" end
-- QB-Core example (uncomment and modify as needed):
-- local success, player = pcall(function()
-- return exports['qb-core']:GetPlayer(serverId)
-- end)
--
-- if success and player and player.PlayerData then
-- -- Check for callsign in metadata
-- if player.PlayerData.metadata and player.PlayerData.metadata.callsign and player.PlayerData.metadata.callsign ~= "NO CALLSIGN" and player.PlayerData.metadata.callsign ~= "" then
-- return player.PlayerData.metadata.callsign
-- end
--
-- -- Check for lastname in charinfo
-- if player.PlayerData.charinfo and player.PlayerData.charinfo.lastname and player.PlayerData.charinfo.lastname ~= "" then
-- return player.PlayerData.charinfo.lastname
-- end
-- end
-- Fallback to FiveM player name
local name = GetPlayerName(serverId)
if not name or name == "" then
return "Player " .. serverId
end
-- Extract callsign from name format "Name S. 2L-319"
local callsign = string.match(name, "%s([%w%-]+%d+)$")
if callsign then
return callsign
end
return name
end,
-- Check if player has siren on (CLIENT ONLY)
-- ============================================================================
-- LVC INTEGRATION VERSION (Default)
--
-- Parameters:
-- lvcSirenState: The current LVC siren state (tracked in shared.lua)
-- 0 = No siren audio (lights only or off)
-- >0 = Siren audio playing (Wail, Yelp, Priority, etc.)
-- ============================================================================
bgSirenCheck = function(lvcSirenState)
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
-- LVC Integration: Check if siren AUDIO is actually playing
-- lvcSirenState > 0 means siren audio (Wail/Yelp/Priority/etc) is active
-- lvcSirenState = 0 means lights-only mode (no audio)
return lvcSirenState and lvcSirenState > 0
end,
--[[
============================================================================
NON-LVC FALLBACK VERSION
============================================================================
Use this version if you DON'T have LVC (Luxart Vehicle Control) installed.
How to use:
1. Comment out the LVC version above
2. Uncomment this entire section (remove the markers)
Note: The lvcSirenState parameter will be nil/0 without LVC, so you can
ignore it and use your own logic.
WARNING: This fallback version cannot distinguish between lights-only mode
and siren audio. It will return true whenever sirens are on (including
lights-only), which may cause false positives.
============================================================================
bgSirenCheck = function(lvcSirenState)
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
-- Check if vehicle has sirens on
if not IsVehicleSirenOn(vehicle) then return false end
-- Check speed (convert m/s to mph) - lowered from 50 to 10 mph for better detection
local speed = GetEntitySpeed(vehicle) * 2.237
if speed <= 10 then return false end
-- Fallback: Just check if siren is on (will return true for lights-only mode too)
return IsVehicleSirenOn(vehicle)
end,
]]
-- Alerts configuration, the first alert is the default alert for the SGN button in-game
alerts = {
[1] = {
name = "SIGNAL 100", -- Alert Name
color = "#d19d00", -- Hex color code for alert
isPersistent = true, -- If true, the alert stays active until cleared
tone = "ALERT_A", -- Corrosponds to a tone defined in client/radios/default/tones.json
},
[2] = {
name = "SIGNAL 3",
color = "#0049d1", -- Hex color code for alert
isPersistent = true, -- If true, the alert stays active until cleared
tone = "ALERT_A", -- Corrosponds to a tone defined in client/radios/default/tones.json
},
[3] = {
name = "Ping",
color = "#0049d1", -- Hex color code for alert
tone = "ALERT_B", -- Corrosponds to a tone defined in client/radios/default/tones.json
},
[4] = {
name = "Boop",
color = "#1c4ba3", -- Hex color code for alert
toneOnly = true, -- If true, only plays tone without showing alert on radio
tone = "BONK", -- Corrosponds to a tone defined in client/radios/default/tones.json
},
},
-- Radio zones and channels
zones = {
[1] = {
name = "Statewide",
nacIds = { "141", "110" },
Channels = {
[1] = {
name = "DISP", -- Channel Name
type = "conventional", -- Channel Type ("conventional" or "trunked")
frequency = 154.755, -- Frequency in MHz
allowedNacs = { "141" }, -- 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 = 54, visibleToNacs = { 141 } } -- GPS settings for this channel, for blip colors reference (https://docs.fivem.net/docs/game-references/blips/#blip-colors)
},
[2] = {
name = "C2C", -- Channel Name
type = "trunked", -- Channel Type ("conventional" or "trunked")
frequency = 856.1125, -- Frequency in MHz
frequencyRange = { 856.000, 859.000 }, -- Frequency range for trunked channels
coverage = 500, -- Coverage in meters
allowedNacs = { "141" }, -- 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 = 25, visibleToNacs = { 141 } } -- GPS settings for this channel
},
[3] = {
name = "10-1",
type = "conventional",
frequency = 154.785,
allowedNacs = { "141" },
scanAllowedNacs = { "110", "200" },
gps = { color = 47, visibleToNacs = { 141 } }
},
[4] = {
name = "OPS-1",
type = "conventional",
frequency = 154.815,
allowedNacs = { "141" },
scanAllowedNacs = { "110", "200" },
gps = { color = 40, visibleToNacs = { 141 } }
},
},
},
[2] = {
name = "Los Santos",
nacIds = { "141" },
Channels = {
[1] = {
name = "DISP",
type = "conventional",
frequency = 460.250,
allowedNacs = { "141" },
scanAllowedNacs = { "110", "200" },
gps = { visibleToNacs = { 141 } }
},
[2] = {
name = "C2C",
type = "trunked",
frequency = 460.325,
frequencyRange = { 460.325, 462.325 },
coverage = 250,
allowedNacs = { "141" },
scanAllowedNacs = { "110", "200" },
gps = { color = 25, visibleToNacs = { 141 } }
},
[3] = {
name = "10-1",
type = "conventional",
frequency = 460.275,
allowedNacs = { "141" },
scanAllowedNacs = { "110", "200" },
gps = { color = 47, visibleToNacs = { 141 } }
},
[4] = {
name = "OPS-1",
type = "conventional",
frequency = 462.450,
allowedNacs = { "50" },
scanAllowedNacs = { "141" },
gps = { color = 40, visibleToNacs = { 141 } }
},
},
},
[3] = {
name = "Blaine County",
nacIds = { "141" },
Channels = {
[1] = {
name = "DISP",
type = "conventional",
frequency = 155.070,
allowedNacs = { "141" },
scanAllowedNacs = { "110", "200" },
gps = {
color = 52,
visibleToNacs = { 141 }
}
},
[2] = {
name = "C2C",
type = "trunked",
frequency = 155.220,
frequencyRange = { 155.220, 157.220 },
coverage = 250,
allowedNacs = { "141" },
scanAllowedNacs = { "110", "200" },
gps = { color = 25, visibleToNacs = { 141 } }
},
[3] = {
name = "10-1",
type = "conventional",
frequency = 155.100,
allowedNacs = { "141" },
scanAllowedNacs = { "110", "200" },
gps = { color = 47, visibleToNacs = { 141 } }
},
[4] = {
name = "OPS-1",
type = "conventional",
frequency = 157.350,
allowedNacs = { "141" },
scanAllowedNacs = { "110", "200" },
gps = { color = 40, visibleToNacs = { 141 } }
},
},
}
}
}