--Implementation of Command Dialogs Panel for DCS objects. --RadioCommandDialogsPanel receives messags (wMessage), world events (world.event), generates internal events and passes them to --CommandDialogsPanel.onEvent(). --Indicates if at least one of the radios is tuned into the given recepient. --Selects and tunes radios to the recepient. local base = _G local DCS = require('DCS') local function clean() base.RadioCommandDialogsPanel = nil base.package.loaded['RadioCommandDialogsPanel'] = nil base.CommandDialogsPanel = nil base.package.loaded['CommandDialogsPanel'] = nil base.CommandDialog = nil base.package.loaded['CommandDialog'] = nil base.CommandMenu = nil base.package.loaded['CommandMenu'] = nil base.StaticMenu = nil base.package.loaded['StaticMenu'] = nil base.StaticList = nil base.package.loaded['StaticList'] = nil base.TabSheetBar = nil base.package.loaded['TabSheetBar'] = nil end module('RadioCommandDialogsPanel') --singleton! local self = base.getfenv() local theRadioCommandDialogsPanel = self local commandDialogsPanel = base.require('CommandDialogsPanel') local commandDialog = base.require('CommandDialog') local utils = base.require('utils') local gettext = base.require("i_18n") local _ = gettext.translate base.setmetatable(base.getfenv(), commandDialogsPanel) --properties --private data local data = { base=base, pUnit = nil, pComm = nil, initialized = false, worldEventHandlers = {}, --World event handles handle world events and passes them into RadioCommandDialogsPanel.onEvent() msgHandlers = {}, --Message handlers converts messages into internal events and passes them into RadioCommandDialogsPanel.onEvent() menus = {}, menuOther = { --Misson command menu name = _('Other'), submenu = { name = _('Other'), items = {} } }, menuEmbarkToTransport = { --Misson command menu name = _('Descent'), submenu = { name = _('Descent'), items = {} } }, --Communication curCommunicatorId = nil, intercomId = nil, communicators = nil, VoIP = false, --Settings showingOnlyPresentRecepients = true, highlighting = true, radioAutoTune = false, recepientInfo = true, rootItem = function() end, } local commById = {} local bBanMouse = false --Internal events local events = { --Created from world events NOTIFY_BIRTH_ON_RAMP_COLD = 10000, NOTIFY_BIRTH_ON_RAMP_HOT = 10001, NOTIFY_BIRTH_ON_RUNWAY = 10002, NOTIFY_BIRTH_ON_HELIPAD_COLD= 10003, NOTIFY_BIRTH_ON_HELIPAD_HOT = 10004, NOTIFY_BIRTH_ON_SHIP_COLD = 10005, NOTIFY_BIRTH_ON_SHIP_HOT = 10006, TAKEOFF = 10007, LANDING = 10008, ENGINE_STARTUP = 10009, ENGINE_SHUTDOWN = 10010, --Created from radio messages STARTUP_PERMISSION_FROM_AIRDROME = 10101, STARTUP_PERMISSION_FROM_HELIPAD = 10102, STARTUP_PERMISSION_FROM_SHIP = 10103, DENY_TAKEOFF_FROM_HELIPAD = 10104, DENY_TAKEOFF_FROM_AIRDROME = 10105, DENY_TAKEOFF_FROM_SHIP = 10106, CLEAR_TO_TAKEOFF_FROM_HELIPAD = 10107, CLEAR_TO_TAKEOFF_FROM_AIRDROME = 10108, CLEAR_TO_TAKEOFF_FROM_SHIP = 10109, --Other START_WP_DIALOG = 10200, START_IR_POINTER_DIALOG = 10201, START_LASER_DIALOG = 10202, } --Menu actions local sendMessage = { perform = function(self, parameters) local messageParameters = {} local command = self.command for i, p in base.pairs(parameters) do if command == nil and self.commandPos == i then command = p else base.table.insert(messageParameters, p) end end if self.parameters then for i, p in base.pairs(self.parameters) do base.table.insert(messageParameters, p) end elseif self.getParameter then base.table.insert(messageParameters, self.getParameter()) end data.pComm:sendRawMessage(command, messageParameters) end } sendMessage.__index = sendMessage function sendMessage.new(command, ...) base.assert(command ~= nil) local newSendMessage = { command = command, parameters = {...} } base.setmetatable(newSendMessage, sendMessage) return newSendMessage end function sendMessage.new2(commandPos, ...) base.assert(commandPos ~= nil) local newSendMessage = { commandPos = commandPos, parameters = {...} } base.setmetatable(newSendMessage, sendMessage) return newSendMessage end function sendMessage.new3(command, getParameter) base.assert(command ~= nil) local newSendMessage = { command = command, getParameter = getParameter } base.setmetatable(newSendMessage, sendMessage) return newSendMessage end local DoMissionAction = { perform = function(self, parameters) base.missionCommands.doAction(self.actionIndex) end } DoMissionAction.__index = DoMissionAction function DoMissionAction.new(actionIndex) local doMissionAction = { actionIndex = actionIndex } base.setmetatable(doMissionAction, DoMissionAction) return doMissionAction end local setBehaviorOption = { perform = function(self, parameters) data.pUnit:getGroup():getController():setOption(self.optionName, self.optionValue) end } function setBehaviorOption:new(optionNameIn, optionValueIn) local instance = { optionName = optionNameIn, optionValue = optionValueIn } self.__index = self base.setmetatable(instance, self) return instance end --communicator features local function findCommunicatorChannel(channels, targetFrequency) for channelNum, frequency in base.pairs(channels) do if base.math.abs(frequency - targetFrequency) < 10000.0 then return channelNum end end return nil end local function checkCommunicator(communicator, targetFrequency, targetModulation) if communicator ~= nil then if communicator.interphone then if targetFrequency == nil or targetModulation == nil then --wired return true end else if targetFrequency == nil or targetModulation == nil then return false elseif (targetModulation == base.Communicator.MODULATION_AM and communicator.AM) or (targetModulation == base.Communicator.MODULATION_FM and communicator.FM) then if communicator.channels then if findCommunicatorChannel(communicator.channels, targetFrequency) ~= nil then return true end end if communicator.range then if communicator.range.min < targetFrequency and targetFrequency < communicator.range.max then return true end elseif communicator.ranges then for rangeNum, range in base.pairs(communicator.ranges) do --base.print('targetFrequency = '..targetFrequency) --base.print('range.min = '..range.min) --base.print('range.max = '..range.max) if range.min < targetFrequency and targetFrequency < range.max then return true end end end end end end return false end local function selectCommunicatorDeviceId(targetCommunicator) if data.intercomId ~= nil and data.communicators ~= nil then -- ground crew (interphone) if targetCommunicator == nil then for communicatorId, communicator in base.pairs(data.communicators) do if checkCommunicator(communicator) then if base.GetDevice(data.intercomId):is_communicator_available(communicatorId) then return communicatorId end end end return nil end -- helper function local function checkRadioCommunicatorAvailaliblity(target, communicator, communicatorId) if target:checkCommunicator() ~= true then return false; end local freqModTbl = target:getFrequenciesModulations() if freqModTbl ~= nil then for transiverId, freqMod in base.pairs(freqModTbl) do if checkCommunicator(communicator, freqMod.frequency, freqMod.modulation) then if base.GetDevice(data.intercomId):is_communicator_available(communicatorId) then return true end end end end return false end -- over-the-air communications (i.e. not interphone) if data.curCommunicatorId == COMMUNICATOR_AUTO then for communicatorId, communicator in base.pairs(data.communicators) do if checkRadioCommunicatorAvailaliblity(targetCommunicator, communicator, communicatorId) then return communicatorId end end elseif data.curCommunicatorId ~= COMMUNICATOR_VOID then local communicatorId = data.curCommunicatorId if checkRadioCommunicatorAvailaliblity(targetCommunicator, data.communicators[communicatorId], communicatorId) then return communicatorId end end end return nil end local function selectAndTuneCommunicator(targetCommunicator) local autoTuneOverride = false local communicatorId = selectCommunicatorDeviceId(targetCommunicator) if communicatorId ~= nil then if base.GetDevice(data.intercomId):easy_comm_override(communicatorId) then autoTuneOverride = false end end if not autoTuneOverride then if not data.radioAutoTune then return nil end end if communicatorId ~= nil then -- A-10C needs an additional setup in the cockpit - Intercom mode switching if not base.GetDevice(data.intercomId):make_setup_for_communicator(communicatorId) then return nil end local communicator = data.communicators[communicatorId] if communicator.interphone then else local commDevice = base.GetDevice(communicatorId) local freqModTbl = targetCommunicator:getFrequenciesModulations() for transiverId, freqMod in base.pairs(freqModTbl) do local haveFreq = false if communicator.channels then local channelNum = findCommunicatorChannel(communicator.channels, freqMod.frequency) if channelNum ~= nil then haveFreq = true commDevice:set_channel(channelNum) end else if commDevice:is_frequency_in_range(freqMod.frequency) then commDevice:set_frequency(freqMod.frequency) haveFreq = true end end if haveFreq == true then local commDevice = base.GetDevice(communicatorId) if communicator.AM and communicator.FM then commDevice:set_modulation(freqMod.modulation) end break end end end if data.curCommunicatorId ~= communicatorId then base.GetDevice(data.intercomId):set_communicator(communicatorId) end return communicatorId end return nil end local RecepientState = { VOID = 0, --no radio TUNED = 1, --selected communicator tuned onto recepient CAN_BE_TUNED = 2, --selected communicator can be tuned onto recepient CANNOT_BE_TUNED = 3 --selected communicator can not be tuned onto recepient due the frequency range or switched off state } local function getRecepientState(targetCommunicator) --base.print('*RADIO* assessing recepient state...') if data.intercomId ~= nil and data.communicators ~= nil then --base.print('*RADIO* looking for device...') local commDeviceId = selectCommunicatorDeviceId(targetCommunicator) if commDeviceId ~= nil then --base.print('*RADIO* found device -> ID=' .. commDeviceId) if data.communicators[commDeviceId].interphone then --base.print('*RADIO* device is interphone') if base.GetDevice(data.intercomId):is_communicator_available(commDeviceId) then --base.print('*RADIO* found match') return RecepientState.TUNED end else base.assert(targetCommunicator ~= nil) local device = base.GetDevice(commDeviceId) if device:is_on() then -- Redesingned this routine on 13/04/2018 as it failed to produce result on anything but first listed frequency -- which is all but 4MHz, also added modulation type comparison as it was completely ignored here. - Made Dragon --base.print('*RADIO* device is on') -- collecting setup from currently active device in the aircraft, I presume local currentFrequency = device:get_frequency() local currentModulation = device:get_modulation() --base.print('*RADIO* selected ' .. data.communicators[commDeviceId].displayName .. ' -> FRQ=' .. currentFrequency .. ' M=' .. currentModulation) -- collecting data from all active ATC transceivers starting with assesing their number local countTargetTransivers = targetCommunicator:countTransivers() --base.print('*RADIO* target has ' .. countTargetTransivers .. ' transivers') for i = 0, countTargetTransivers-1 do -- collecting individual setup through all ATC transceivers local targetTransiverFrequency = targetCommunicator:getFrequency(i) local targetTransiverModulation = targetCommunicator:getModulation(i) --base.print('*RADIO* assessing transiver No. ' .. i .. ' -> FRQ=' .. targetTransiverFrequency .. ' M=' .. targetTransiverModulation) -- assessing if the two would match if (currentModulation==base.Communicator.MODULATION_AM_AND_FM) or -- assuming that if either of the connecting devices have two modulation types active (targetTransiverModulation==base.Communicator.MODULATION_AM_AND_FM) or -- they would find a way to match (currentModulation==targetTransiverModulation) then -- otherwise they should coincide if base.math.abs(currentFrequency - targetTransiverFrequency) < 10000 then if base.GetDevice(data.intercomId):is_communicator_available(commDeviceId) then --base.print('*RADIO* found match') return RecepientState.TUNED end end end end end end end --base.print('*RADIO* can not connect') return RecepientState.CANNOT_BE_TUNED else --base.print('*RADIO* presumed hardwired') return RecepientState.TUNED end end --[[ local function getInterphoneRecepientState() if data.curCommunicatorId == nil then for communicatorId, communicator in base.pairs(data.communicators) do if data.communicators[data.curCommunicatorId].interphone then return RecepientState.TUNED end end else if data.communicators[data.curCommunicatorId].interphone then return RecepientState.TUNED end end return RecepientState.CANNOT_BE_TUNED end --]] local colorByRecepientState = { [RecepientState.VOID] = utils.COLOR.WHITE, [RecepientState.TUNED] = utils.COLOR.WHITE, [RecepientState.CAN_BE_TUNED] = utils.COLOR.LIGHT_GRAY, [RecepientState.CANNOT_BE_TUNED] = utils.COLOR.DARK_GRAY } local function getRecepientColor(targetCommunicator) return data.highlighting and colorByRecepientState[getRecepientState(targetCommunicator)] end local speech = base.require('speech') --Utilities for variable recepients list local function makeItemByCommunicator(pCommunicator, submenu) base.assert(submenu ~= nil) local commName local selfCoalition = data.pUnit:getCoalition() local callsignStr = speech.protocols[speech.protocolByCountry[selfCoalition] or speech.defaultProtocol]:makeCallsignString(pCommunicator) if pCommunicator:getUnit():hasAttribute("Ships") or callsignStr == nil or pCommunicator:getUnit():hasAttribute("Airfields") then callsignStr = pCommunicator:getUnit():getDesc().displayName end if pCommunicator:getUnit():hasAttribute("Ships") and data.pUnit:canShipLanding() and not data.pUnit:OldCarrierMenuShow() then -- need to change wMsgLeaderInbound command to wMsgLeaderInboundCarrier to select correct role. if submenu.items then for k,v in base.pairs(submenu.items) do if v and v.command and v.command.command == base.Message.wMsgLeaderInbound then v.command.command = base.Message.wMsgLeaderInboundCarrier end end end end local frequency = pCommunicator:getFrequency() local modulation = pCommunicator:getModulation() --base.print('pCommunicator:getCallsign() = '..base.tostring(pCommunicator:getCallsign())) --base.print('callsignStr = '..base.tostring(callsignStr)..', frequency = '..base.tostring(frequency)..', modulation = '..base.tostring(modulation)) if data.recepientInfo and frequency ~= nil and modulation ~= nil then -- Version 2.0: -- Created this on 13/04/2018 in attempt to list an appropriate, currently tunable frequency in station list menu. - Made Dragon local function to25KString ( frequency ) local frequency = base.math.floor(frequency / 1000.0) / 1000 local frequency25Khz = 0.025 * base.math.floor(frequency / 0.025 + 0.5) local frequencyStr = base.tostring(frequency25Khz) return frequencyStr end commName = callsignStr or _('Unknown') local availableTransivers = pCommunicator:countTransivers() local commDeviceId = selectCommunicatorDeviceId(pCommunicator) if commDeviceId ~= nil then local device = base.GetDevice(commDeviceId) local deviceFrequency = device:get_frequency() local closestMatchingFrequencyId = 0 local delta = base.math.abs(deviceFrequency - frequency) for i = 0, availableTransivers-1 do local d = base.math.abs(deviceFrequency - pCommunicator:getFrequency(i)) if d 0 then local new_menu_item = { name = new_menu_name, menuUnit = unit, submenu = { name = new_menu_name, items = {} } } local maxListItems = 10 local counter = 0 for index, cargo in base.pairs(Cargos) do counter = counter + 1 local CargoItem = makeItemByCargo(cargo, child_menu_item.submenu) base.table.insert(new_menu_item.submenu.items, CargoItem) if counter >= maxListItems then break end end return new_menu_item end end end end local function chooseDescentItem(pDescent, pHel) pDescent:embarking(pHel:getObjectID()) end local function makeItemEmbarkDescent(pDesc, pHel) local descName = pDesc:getName() local size = pDesc:getSize() descName = descName.."["..size.."]" local dialogIsOpened = theRadioCommandDialogsPanel:getDialogFor(pDesc:tonumber()) ~= nil return { name = descName, desc = pDesc, isDialogOpened = function() return self:getDialogFor(pDesc:tonumber()) ~= nil end, submenu = nil, command = { perform = function(self) chooseDescentItem(pDesc, pHel) end }, parameter = pDesc } end local prevMark = nil local function makeItemMarkEmbarkDescent(pDesc, pHel) local descName = pDesc:getName() local size = pDesc:getSize() descName = descName.."["..size.."]" local dialogIsOpened = theRadioCommandDialogsPanel:getDialogFor(pDesc:tonumber()) ~= nil return { name = descName, desc = pDesc, isDialogOpened = function() return self:getDialogFor(pDesc:tonumber()) ~= nil end, submenu = nil, command = { perform = function(self) if prevMark ~= nil then prevMark:markGroup(false) end pDesc:markGroup(true) prevMark = pDesc end }, parameter = pDesc } end local function makeItemDisembarkDescent(pDesc, pHel) local descName = pDesc:getName() local id = pDesc:getID() local dialogIsOpened = theRadioCommandDialogsPanel:getDialogFor(pDesc:tonumber()) ~= nil return { name = descName, desc = pDesc, isDialogOpened = function() return self:getDialogFor(pDesc:tonumber()) ~= nil end, submenu = nil, command = { perform = function(self) pHel:disembarking(id) end }, parameter = pDesc } end local function markDisembarkZoneDescent(pDesc, pHel) local descName = pDesc:getName() local id = pDesc:getID() local dialogIsOpened = theRadioCommandDialogsPanel:getDialogFor(pDesc:tonumber()) ~= nil return { name = descName, desc = pDesc, isDialogOpened = function() return self:getDialogFor(pDesc:tonumber()) ~= nil end, submenu = nil, command = { perform = function(self) pHel:markDisembarkingTask(id) end }, parameter = pDesc } end local function buildMultyEmbraksMenu(Descent, count, unit, menu, child_menu_item) local maxListItems = 10 if count < #Descent + 1 then local tmp = base.math.fmod(count, maxListItems) if tmp == 0 then local nextItems = { name = _("Next"), marker = "Embark", submenu = { name = _("Embark"), items = {}, marker = "Embark", }, onEnterMenu = function(menuItem, commandMenu) if child_menu_item.submenu.onEnterMenu then child_menu_item.submenu.onEnterMenu(commandMenu, unit) end end, onLeaveMenu = function(commandMenu) if child_menu_item.submenu.onLeaveMenu then child_menu_item.submenu.onLeaveMenu(commandMenu) end end, updatePosition = function(commandMenu) if child_menu_item.submenu.updatePosition then child_menu_item.submenu.updatePosition(commandMenu) end end, } base.table.insert(menu.submenu.items, nextItems) local desc = Descent[count] local DescItem = makeItemEmbarkDescent(desc, unit) base.table.insert(nextItems.submenu.items, DescItem) buildMultyEmbraksMenu(Descent, count+1, unit, nextItems, child_menu_item) else local desc = Descent[count] local DescItem = makeItemEmbarkDescent(desc, unit) base.table.insert(menu.submenu.items, DescItem) buildMultyEmbraksMenu(Descent, count+1, unit, menu, child_menu_item) end end end local function buildMultyMarkEmbraksMenu(Descent, count, unit, menu, child_menu_item) local maxListItems = 10 if count < #Descent + 1 then local tmp = base.math.fmod(count, maxListItems) if tmp == 0 then local nextItems = { name = _("Next"), marker = "Embark", submenu = { name = _("Mark"), items = {}, marker = "Embark", }, onEnterMenu = function(menuItem, commandMenu) if child_menu_item.submenu.onEnterMenu then child_menu_item.submenu.onEnterMenu(commandMenu, unit) end end, onLeaveMenu = function(commandMenu) if child_menu_item.submenu.onLeaveMenu then child_menu_item.submenu.onLeaveMenu(commandMenu) end end, updatePosition = function(commandMenu) if child_menu_item.submenu.updatePosition then child_menu_item.submenu.updatePosition(commandMenu) end end, } base.table.insert(menu.submenu.items, nextItems) local desc = Descent[count] local DescItem = makeItemMarkEmbarkDescent(desc, unit) base.table.insert(nextItems.submenu.items, DescItem) buildMultyMarkEmbraksMenu(Descent, count+1, unit, nextItems, child_menu_item) else local desc = Descent[count] local DescItem = makeItemMarkEmbarkDescent(desc, unit) base.table.insert(menu.submenu.items, DescItem) buildMultyMarkEmbraksMenu(Descent, count+1, unit, menu, child_menu_item) end end end local function buildDescentsMenu(Descent, new_menu_name, child_menu_item, unit) local main_menu_item = { name = new_menu_name, menuUnit = unit, submenu = { name = new_menu_name, items = {}, } } local markDescent = base.coalition.getAllDescents(data.pUnit:getObjectID(), false) if Descent ~= nil or (markDescent ~= nil and #markDescent > 0) then if #Descent > 0 then local embarkMenu = { name = _("Embark"), menuUnit = unit, marker = "Embark", submenu = { marker = "Embark", name = _("Embark"), items = {} }, onEnterMenu = function(menuItem, commandMenu) if child_menu_item.submenu.onEnterMenu then child_menu_item.submenu.onEnterMenu(commandMenu, unit) end end, onLeaveMenu = function(commandMenu) if child_menu_item.submenu.onLeaveMenu then child_menu_item.submenu.onLeaveMenu(commandMenu) end end, updatePosition = function(commandMenu) if child_menu_item.submenu.updatePosition then child_menu_item.submenu.updatePosition(commandMenu) end end, } local EmbarkItem = { name = _("Embarking"), menuUnit = unit, marker = "Embark", submenu = { name = _("Embarking"), marker = "Embark", items = {} }, onEnterMenu = function(menuItem, commandMenu) if child_menu_item.submenu.onEnterMenu then child_menu_item.submenu.onEnterMenu(commandMenu, unit) end end, onLeaveMenu = function(commandMenu) if child_menu_item.submenu.onLeaveMenu then child_menu_item.submenu.onLeaveMenu(commandMenu) end end, updatePosition = function(commandMenu) if child_menu_item.submenu.updatePosition then child_menu_item.submenu.updatePosition(commandMenu) end end, } local EmbarkMarkItem = { name = _("Mark"), menuUnit = unit, marker = "Embark", submenu = { marker = "Embark", name = _("Mark"), items = {} }, onEnterMenu = function(menuItem, commandMenu) if child_menu_item.submenu.onEnterMenu then child_menu_item.submenu.onEnterMenu(commandMenu, unit) end end, onLeaveMenu = function(commandMenu) if child_menu_item.submenu.onLeaveMenu then child_menu_item.submenu.onLeaveMenu(commandMenu) end end, updatePosition = function(commandMenu) if child_menu_item.submenu.updatePosition then child_menu_item.submenu.updatePosition(commandMenu) end end, } base.table.insert(main_menu_item.submenu.items, embarkMenu) if Descent ~= nil then base.table.insert(embarkMenu.submenu.items, EmbarkItem) buildMultyEmbraksMenu(Descent, 1, unit, EmbarkItem, child_menu_item) end if markDescent ~= nil and #markDescent > 0 then base.table.insert(embarkMenu.submenu.items, EmbarkMarkItem) buildMultyMarkEmbraksMenu(markDescent, 1, unit, EmbarkMarkItem, child_menu_item) end end end local arrOnBoard = base.coalition.getDescentsOnBoard(unit:getObjectID()) if arrOnBoard ~=nil and #arrOnBoard > 0 then local disEmbarkMenu = { name = _("Disembark"), menuUnit = unit, submenu = { name = _("Disembark"), items = {} } } local disEmbItem = { name = _("Disembark"), menuUnit = unit, submenu = { name = _("Disembark"), items = {} } } local disEmbMarkItem = { name = _("Mark zone"), menuUnit = unit, submenu = { name = _("Disembark"), items = {} } } base.table.insert(disEmbarkMenu.submenu.items, disEmbItem) base.table.insert(disEmbarkMenu.submenu.items, disEmbMarkItem) base.table.insert(main_menu_item.submenu.items, disEmbarkMenu) local maxListItems = 10 local counter = 0 for index, desc in base.pairs(arrOnBoard) do counter = counter + 1 local DescItem = makeItemDisembarkDescent(desc, unit) base.table.insert(disEmbItem.submenu.items, DescItem) if counter >= maxListItems then break end end local maxListItems = 10 local counter = 0 for index, desc in base.pairs(arrOnBoard) do counter = counter + 1 local DescItem = markDisembarkZoneDescent(desc, unit) base.table.insert(disEmbMarkItem.submenu.items, DescItem) if counter >= maxListItems then break end end end return main_menu_item end local function makeItemByCommunicatorATC2(pCommunicator, submenu) base.assert(submenu ~= nil) local commName local selfCoalition = data.pUnit:getCoalition() local callsignStr = speech.protocols[speech.protocolByCountry[selfCoalition] or speech.defaultProtocol]:makeCallsignString(pCommunicator) --if pCommunicator:getUnit():hasAttribute("Ships") or callsignStr == nil then callsignStr = pCommunicator:getUnit():getDesc().displayName --end local frequency = pCommunicator:getFrequency() local modulation = pCommunicator:getModulation() --base.print('callsignStr = ', callsignStr ) --base.print('pCommunicator:getCallsign() = '..base.tostring(pCommunicator:getCallsign())) --base.print('callsignStr = '..base.tostring(callsignStr)..', frequency = '..base.tostring(frequency)..', modulation = '..base.tostring(modulation)) if data.recepientInfo and frequency ~= nil and modulation ~= nil then local frequency = base.math.floor(frequency / 1000.0) / 1000 local frequency25Khz = 0.025 * base.math.floor(frequency / 0.025 + 0.5) local frequencyStr = base.tostring(frequency25Khz)..' MHz' local modulationStr = (modulation == base.Communicator.MODULATION_AM) and "AM" or "FM" commName = (callsignStr or '')..'-'..frequencyStr..' '..modulationStr if pCommunicator:getUnit():getCoalition() ~= selfCoalition then commName = commName..' '.._('(Neutral)') end else commName = callsignStr end base.assert(commName ~= nil) local dialogIsOpened = theRadioCommandDialogsPanel:getDialogFor(pCommunicator:tonumber()) ~= nil return { name = commName, color = { get = function(self) return getRecepientColor(pCommunicator) end }, receiver = pCommunicator, isDialogOpened = function() return self:getDialogFor(pCommunicator:tonumber()) ~= nil end, command = { perform = function(self) selectAndTuneCommunicator(pCommunicator) local dialogIsOpened = theRadioCommandDialogsPanel:getDialogFor(pCommunicator:tonumber()) ~= nil if dialogIsOpened then theRadioCommandDialogsPanel:switchToDialogFor(pCommunicator:tonumber()) end end }, submenu = not dialogIsOpened and submenu or nil, parameter = pCommunicator } end local function buildRecepientsMenuATC2(recepients, new_menu_name, child_menu_item) if #recepients == 1 then local communicator = recepients[1]:getCommunicator() local item = makeItemByCommunicatorATC2(communicator, child_menu_item.submenu) item.name = new_menu_name..' - '..item.name return item elseif #recepients > 1 then local new_menu_item = { name = new_menu_name, submenu = { name = new_menu_name, items = {} } } local maxListItems = 10 local counter = 0 for index, recepient in base.pairs(recepients) do local communicator = recepient:getCommunicator() if communicator:hasTransiver() then counter = counter + 1 local recepientItem = makeItemByCommunicatorATC2(communicator, child_menu_item.submenu) base.table.insert(new_menu_item.submenu.items, recepientItem) if counter >= maxListItems then break end end end return new_menu_item end end local function buildRecepientsMenu(recepients, new_menu_name, child_menu_item) if #recepients == 1 then local communicator = recepients[1]:getCommunicator() if communicator~= nil then local item = makeItemByCommunicator(communicator, child_menu_item.submenu) item.name = new_menu_name..' - '..item.name return item end elseif #recepients > 1 then local new_menu_item = { name = new_menu_name, submenu = { name = new_menu_name, items = {} } } local maxListItems = 10 local counter = 0 for index, recepient in base.pairs(recepients) do local communicator = recepient:getCommunicator() if communicator ~= nil and communicator:hasTransiver() then counter = counter + 1 local recepientItem = makeItemByCommunicator(communicator, child_menu_item.submenu) base.table.insert(new_menu_item.submenu.items, recepientItem) if counter >= maxListItems then break end end end return new_menu_item end end local function checkRecepients(recepients) local recepientStateCounter = nil for index, recepient in base.pairs(recepients) do recepientStateCounter = recepientStateCounter or { [RecepientState.VOID] = 0, [RecepientState.TUNED] = 0, [RecepientState.CAN_BE_TUNED] = 0, [RecepientState.CANNOT_BE_TUNED] = 0, } local communicator = recepient:getCommunicator() local recepientState = getRecepientState(communicator) recepientStateCounter[recepientState] = recepientStateCounter[recepientState] + 1 end return recepientStateCounter end local function getRecepientsState(recepients) if data.intercomId ~= nil and data.communicators ~= nil then local recepientsState = checkRecepients(recepients) if recepientsState == nil then return RecepientState.VOID elseif recepientsState[RecepientState.TUNED] > 0 then return RecepientState.TUNED elseif recepientsState[RecepientState.CAN_BE_TUNED] > 0 then return RecepientState.CAN_BE_TUNED elseif recepientsState[RecepientState.CANNOT_BE_TUNED] > 0 then return RecepientState.CANNOT_BE_TUNED else return RecepientState.VOID end else return RecepientState.TUNED end end local function getRecepientsColor(recepients) return data.highlighting and colorByRecepientState[getRecepientsState(recepients)] end local function staticParamsEvent(event, params) return { type = base.Message.type.TYPE_CONSTRUCTABLE, playMode = base.Message.playMode.PLAY_MODE_LIMITED_DURATION, event = event, params = params, perform = function(self,parameters) data.pComm:sendMessage({ type = self.type, playMode = self.playMode, event = self.event, parameters = self.params}) end } end --Dialogs local DialogStartTrigger = { new = function(self, dialogIn) return commandDialogsPanel.DialogStartTrigger.new(self, theRadioCommandDialogsPanel, dialogIn) end, run = function(self, senderId) local pCommunicator = commById[senderId] local function tuneCommunicator() selectAndTuneCommunicator(pCommunicator) end local color = { get = function(self) return getRecepientColor(pCommunicator) end } --base.print('DialogStartTrigger run '..self.dialog.name) local dialog = self.commandDialogsPanel:openDialog(self.dialog, senderId, tuneCommunicator, color) if dialog then dialog.isAvailable = function(self) if not data.showingOnlyPresentRecepients then --base.print('DialogStartTrigger run: isAvailable true') return true else local result = data.curCommunicatorId == COMMUNICATOR_AUTO or data.communicators == nil or #data.communicators == 0 or ( data.curCommunicatorId ~= COMMUNICATOR_VOID and checkCommunicator( data.communicators[data.curCommunicatorId], pCommunicator:getFrequency(), pCommunicator:getModulation()) ) --[[ if result then base.print('DialogStartTrigger run: isAvailable = true') else base.print('DialogStartTrigger run: isAvailable = false') end ]] return result; end end --base.print('DialogStartTrigger run: dialog = true') end end } DialogStartTrigger.__index = DialogStartTrigger base.setmetatable(DialogStartTrigger, commandDialogsPanel.DialogStartTrigger) --Private local function hasUnit() return true --return data.pUnit ~= nil and data.pUnit:isExist() end local function getMessageColor(pMsgSender, pMsgReceiver, event) return utils.COLOR.WHITE end --Callbacks --CommandMenus & CommandDialogs events handlers function onDialogCommand(self, dialog, command, parameters) --base.print('onCommand('..base.tostring(command)..')') self.mainCaption:setVisible(false) banMouse(self,false) dialog:setMainMenu() if command then command:perform(parameters) end end function banMouse(self, on) if bBanMouse ~= on then bBanMouse = on if on then DCS.lockAllMouseInput() else DCS.unlockMouseInput() end end self.window:setFocused(on) end function onMenuShow(self, menu) self.window:setVisible(true) self.mainCaption:setVisible(true) acquireCommandMenu() banMouse(self,true) end function onMenuHide(self, menu) self.window:setVisible(false) self.mainCaption:setVisible(false) freeCommandMenu() banMouse(self, false) end local function setUnit_(pUnitIn) data.pUnit = pUnitIn data.pComm = pUnitIn and pUnitIn:getCommunicator() data.curCommunicatorId = COMMUNICATOR_VOID end local worldEventHandler = { onEvent = function(self, event) for index, handler in base.pairs(data.worldEventHandlers) do local internalEvent, sender, recepient = handler:onEvent(event) if internalEvent ~= nil then if sender ~= nil then commById[sender:tonumber()] = sender end if recepient ~= nil then commById[recepient:tonumber()] = recepient end theRadioCommandDialogsPanel:onEvent(internalEvent, sender and sender:tonumber(), recepient and recepient:tonumber()) end end end } --Interface --Interface for ICommandDialogsPanel in ICommandDialogsPanel.h local function setEasyComm(on) data.showingOnlyPresentRecepients = on data.highlighting = on data.radioAutoTune = false data.recepientInfo = on end local function updateMainCaption() if not data.initialized then return end if not hasUnit() then return end local mainCaption = '' if data.curCommunicatorId == COMMUNICATOR_AUTO then mainCaption = _('AUTO') elseif data.curCommunicatorId ~= COMMUNICATOR_VOID then local communicator = data.communicators[data.curCommunicatorId] if communicator then mainCaption = communicator.displayName else mainCaption = '???' end end if data.VoIP then mainCaption = mainCaption.._(' ...VoIP...') end commandDialogsPanel.setMainCaption(self, mainCaption) end local function TERMINATE() return commandDialog.makeDialogTermination() end local function TO_DIALOG_STAGE(dialogsData, dialogName, stageName, pushToStack) local stageMenu = dialogsData.dialogs[dialogName].menus[stageName] return commandDialog.makeStageTransition(stageMenu, stageName, pushToStack) end local function RETURN_TO_STAGE(stage) return commandDialog.makeStageReturn() end -- local function buildOthers(self, menu) -- end local count=0 local heightMenu=0 function initialize(pUnitIn, easyComm, intercomId, communicators) count=count+1 --base.print("init count"..count) base.assert(COMMUNICATOR_VOID ~= nil) base.assert(COMMUNICATOR_AUTO ~= nil) data.curCommunicatorId = COMMUNICATOR_VOID base.assert(data.initialized == false) setEasyComm(easyComm == nil or easyComm) setUnit_(pUnitIn) data.intercomId = data.intercomId or intercomId base.assert(data.communicators == nil) data.communicators = {} if communicators ~= nil then for communicatorId, communicator in base.pairs(communicators) do data.communicators[communicatorId] = communicator end end local dialogsData = { dialogs = {}, triggers = {} } local function TO_STAGE(dialogName, stageName, pushToStack) return TO_DIALOG_STAGE(dialogsData, dialogName, stageName, pushToStack) end local groundUnitScriptNameDefault = 'Scripts/UI/RadioCommandDialogPanel/Config/GroundUnit.lua' local specificScriptName = nil if pUnitIn ~= nil then specificScriptName = pUnitIn:hasAttribute('Air') and base.Objects[pUnitIn:getTypeName()].HumanCommPanelPath or groundUnitScriptNameDefault end local scriptNameDefault = 'Scripts/UI/RadioCommandDialogPanel/Config/default.lua' local env = { table = base.table, math = base.math, pairs = base.pairs, getfenv = base.getfenv, require = base.require, assert = base.assert, print = base.print, tostring = base.tostring, string = base.string, _ = _, db = base.db, world = base.world, Object = base.Object, Airbase = base.Airbase, Message = base.Message, coalition = base.coalition, MissionResourcesDialog = base.MissionResourcesDialog, utils = base.utils, data = data, getSelectedReceiver = function() return self.curDialogIt.element_.receiver end, RecepientState = RecepientState, getRecepientsState = getRecepientsState, getRecepientColor = getRecepientColor, getRecepientsColor = getRecepientsColor, selectAndTuneCommunicator = selectAndTuneCommunicator, setBehaviorOption = setBehaviorOption, sendMessage = sendMessage, buildRecepientsMenu = buildRecepientsMenu, buildRecepientsMenuATC2 = buildRecepientsMenuATC2, buildCargosMenu = buildCargosMenu, buildDescentsMenu = buildDescentsMenu, staticParamsEvent = staticParamsEvent, events = events, dialogsData = dialogsData, TERMINATE = TERMINATE, TO_STAGE = TO_STAGE, RETURN_TO_STAGE = RETURN_TO_STAGE, DialogStartTrigger = DialogStartTrigger } local scriptName = specificScriptName or scriptNameDefault if scriptName ~= nil then --base.print('Loading command panel from \"'..scriptName..'\"') local aircraftRadioCommandPanel, errMsg = utils.loadfileIn(scriptName, env) if aircraftRadioCommandPanel == nil then base.error(errMsg) end aircraftRadioCommandPanel() end heightMenu = commandDialogsPanel.initialize(self, data.menus, data.rootItem, dialogsData) base.world.addEventHandler(worldEventHandler) self:toggle(true) data.initialized = true setHeightCommandMenu( heightMenu ) end function setIntercom(deviceId) data.intercomId = deviceId end function addRadio(deviceId, communicatorData) base.assert(communicatorData ~= nil) if not communicatorData.interphone then base.assert(communicatorData.AM or communicatorData.FM) base.assert(communicatorData.range or communicatorData.ranges or communicatorData.channels) end base.assert(communicatorData.displayName) data.communicators[deviceId] = communicatorData end function release() --base.assert(data.initialized == true) if not data.initialized then return end setShowMenu(false) self:toggle(false) data.curCommunicatorId = nil data.intercomId = nil data.communicators = nil data.pUnit = nil data.pComm = nil commById = {} -- commandDialogsPanel.release(self) base.world.removeEventHandler(worldEventHandler) data.initialized = false end function setUnit(pUnitIn) base.assert(pUnitIn ~= nil) commandDialogsPanel:clear() setUnit_(pUnitIn) end function clear(self) if not data.initialized then return end commandDialogsPanel.clear(self) end function updateSelfMenu() if not data.initialized then return end --base.print('RadioCommanddialogPanel.updateSelfMenu('..base.tostring(self)..')') commandDialogsPanel.updateCurrentMenu(self); end function setShowMenu(on) --base.print('!!!!RadioCommandDialogsPanel: setShowMenu on = '..base.tostring(on)) --base.print(base.debug.traceback()) if not data.initialized then return end if not hasUnit() then return end banMouse(self, on) self.mainCaption:setVisible(on or data.VoIP) commandDialogsPanel.setShowMenu(self, on) end function selectAndTuneCommunicatorFor(pCommunicator) if not data.initialized then return end selectAndTuneCommunicator(pCommunicator) end function setCommunicatorId(curCommunicatorIdIn) data.curCommunicatorId = curCommunicatorIdIn if self.showMenu then self.curDialogIt.element_:setMainMenu() end updateMainCaption() end function setVoIP(on) data.VoIP = on updateMainCaption() self.mainCaption:setVisible(self.showMenu or data.VoIP) banMouse(self, self.showMenu or data.VoIP) end function getCommunicatorId() return data.curCommunicatorId end function onCommunicatorDeath(pCommunicator) if not data.initialized then return end self:closeSenderDialogs(pCommunicator:tonumber()) end function switchToMainMenu() if not data.initialized then return end commandDialogsPanel.switchToMainMenu(self) end function switchToNextDialog() if not data.initialized then return end commandDialogsPanel.switchToNextDialog(self) end function isVisible() return data.initialized and commandDialogsPanel.isMenuVisible(self) end function getSenderName(senderId) local selfCoalition = data.pUnit:getCoalition() local pCommunicator = commById[senderId] local callsignStr = speech.protocols[speech.protocolByCountry[selfCoalition] or speech.defaultProtocol]:makeCallsignString(pCommunicator) return callsignStr end --Receives radio message and generates event function onMsgStart(pMessage, pRecepient, text) if not data.initialized then return end local pMsgSender = pMessage:getSender() local pMsgReceiver = pMessage:getReceiver() local event = pMessage:getEvent() base.assert(pMsgSender.id_ ~= nil) local ttt = { id_ = pMsgSender.id_ } base.setmetatable(ttt, base.getmetatable(pMsgSender) ) base.assert(pMsgSender == ttt) if pMsgSender ~= nil then commById[pMsgSender:tonumber()] = pMsgSender end if pMsgReceiver ~= nil then commById[pMsgReceiver:tonumber()] = pMsgReceiver end --base.print('RadioCommandDialogsPanel:onMsgStart()') --base.print('data.pComm = '..base.tostring(data.pComm)) --base.print('pMsgSender = '..base.tostring(pMsgSender)) --base.print('pMsgReceiver = '..base.tostring(pMsgReceiver)) --base.print('pRecepient = '..base.tostring(pRecepient)) --base.print('event = '..base.tostring(event)) if data.pComm == nil or pRecepient ~= data.pComm then return end local textColor = getMessageColor(pMsgSender, pMsgReceiver, event) if pMsgReceiver == data.pComm or pMsgSender == data.pComm then --Checking if message induces internal event(s) for msgHandlerIndex, msgHandler in base.pairs(data.msgHandlers) do local internalEvent, receiverAsRecepient = msgHandler:onMsg(pMessage, pRecepient) if internalEvent ~= nil then self:onEvent(internalEvent, pMsgSender and pMsgSender, pMsgReceiver:tonumber() and pMsgReceiver:tonumber(), receiverAsRecepient) end end --Processing message by dialog(s) FSM self:onEvent(event, pMsgSender and pMsgSender:tonumber(), pMsgReceiver and pMsgReceiver:tonumber()) end --Processing message by GUI if pMsgReceiver == data.pComm or pMsgSender == data.pComm then commandDialogsPanel.onMsgStart(self, pMsgSender:tonumber(), pMsgReceiver and pMsgReceiver:tonumber(), text, textColor) end end function onMsgFinish(pMessage, pRecepient, text) if not data.initialized then return end local pMsgSender = pMessage:getSender() local pMsgReceiver = pMessage:getReceiver() if pMsgSender ~= nil then commById[pMsgSender:tonumber()] = pMsgSender end if pMsgReceiver ~= nil then commById[pMsgReceiver:tonumber()] = pMsgReceiver end if pMsgReceiver == data.pComm or pMsgSender == data.pComm then commandDialogsPanel.onMsgFinish(self, pMsgSender:tonumber(), pMsgReceiver and pMsgReceiver:tonumber(), text) end end --Receives event of radio message without a message recepetion iself. It is assumed that the message is sent to the player. --Generates event. function onMsgEvent(event, pMsgSender) base.assert(pMsgSender ~= nil) if pMsgSender ~= nil then commById[pMsgSender:tonumber()] = pMsgSender end --checking if message induces internal event(s) for msgHandlerIndex, msgHandler in base.pairs(data.msgHandlers) do if msgHandler.onMsgEvent ~= nil then local internalEvent = msgHandler:onMsgEvent(event, pMsgSender, data.pComm) if internalEvent ~= nil then self:onEvent(internalEvent, pMsgSender and pMsgSender:tonumber(), data.pComm:tonumber()) end end end --processing message by dialog(s) FSM self:onEvent(event, pMsgSender and pMsgSender:tonumber(), data.pComm:tonumber()) end function selectMenuItem(num) commandDialogsPanel.selectMenuItem(self, num) end --Other submenu local function findItemByName(menu, name) for itemIndex, item in base.pairs(menu.items) do if item.name == name then return itemIndex, item end end end local function getSomeSubMenu(path, primaryMenuItem) local menu = data[primaryMenuItem] if menu and menu.submenu then menu=menu.submenu if path ~= nil then for index, name in base.pairs(path) do local itemIndex, item = findItemByName(menu, name) menu = item.submenu end end else menu=data[primaryMenuItem] end return menu end local function copyPath(src) local dst = {} if src ~= nil then for index, name in base.pairs(src) do base.table.insert(dst, name) end end return dst end local function addSomeMenuItem(item, path, primaryMenuItem) local menu = getSomeSubMenu(path, primaryMenuItem) base.table.insert(menu.items, item) local result = copyPath(path) base.table.insert(result, item.name) return result end function addSomeSubmenu(name, path, primaryMenuItem) local item = { name = name, submenu = { name = name, items = {} }} return addSomeMenuItem(item, path, primaryMenuItem) end local function addSomeCommand_(name, command, path, primaryMenuItem) local item = { name = name, command = command} return addSomeMenuItem(item, path, primaryMenuItem) end function removeSomeMenuItem(path, primaryMenuItem) if data[primaryMenuItem] then if path == nil or #path == 0 then data[primaryMenuItem].items = {} return nil else local result = copyPath(path) local name = path[#path] base.table.remove(result) local menu = getSomeSubMenu(result,primaryMenuItem) local itemIndex, item = findItemByName(menu, name) base.table.remove(menu.items, itemIndex) return result end end end function clearSomeMenu(primaryMenuItem) local menu = data[primaryMenuItem] if menu then menu.items = {} if menu.submenu then menu = menu.submenu local menuItems = menu.items local countMenu = #menuItems while countMenu > 0 do countMenu = countMenu - 1 base.table.remove(menu.items) end end end end function addSomeCommand(name, actionIndex, path, primaryMenuItem) return addSomeCommand_(name, DoMissionAction.new(actionIndex), path, primaryMenuItem) end function getDataParameter(parameter) local result=data[parameter] return result end function newMessage(command, ...) return sendMessage.new(command, ...) end function selectCommunicator(communicator) selectAndTuneCommunicator(communicator) end --other menu functions--------------------- local function getOtherSubMenu(path) return getSomeSubMenu(path, 'menuOther') end function addOtherCommand(name, actionIndex, path) return addSomeCommand(name, actionIndex, path, 'menuOther') end function clearOtherMenu() clearSomeMenu('menuOther') end function removeOtherMenuItem(path) removeSomeMenuItem(path, 'menuOther') end local function addOtherCommand_(name, command, path) return addSomeCommand_(name, command, path, 'menuOther') end function addOtherSubmenu(name, path) return addSomeSubmenu(name, path, 'menuOther') end local function addOtherMenuItem(item, path) return addSomeMenuItem(item, path, 'menuOther') end --other menu functions end--------------------- if not ED_FINAL_VERSION then function reload() local pSavedUnit = data.pUnit local easyComm = data.showingOnlyPresentRecepients or data.highlighting or data.radioAutoTune or data.recepientInfo local CONST_COMMUNICATOR_VOID = COMMUNICATOR_VOID local CONST_COMMUNICATOR_AUTO = COMMUNICATOR_AUTO local intercomId = data.intercomId local communicators = data.communicators if data.initialized then theRadioCommandDialogsPanel.release() end local acquireCommandMenu = self.acquireCommandMenu local freeCommandMenu = self.freeCommandMenu local setHeightCommandMenu = self.setHeightCommandMenu local selectMouseMenuItem = self.selectMouseMenuItem clean() base.dofile('Scripts/UI/RadioCommandDialogPanel/RadioCommandDialogsPanel.lua') base.RadioCommandDialogsPanel.COMMUNICATOR_VOID = CONST_COMMUNICATOR_VOID base.RadioCommandDialogsPanel.COMMUNICATOR_AUTO = CONST_COMMUNICATOR_AUTO base.RadioCommandDialogsPanel.initialize(pSavedUnit, easyComm, intercomId, communicators) base.RadioCommandDialogsPanel.acquireCommandMenu = acquireCommandMenu base.RadioCommandDialogsPanel.freeCommandMenu = freeCommandMenu base.RadioCommandDialogsPanel.setHeightCommandMenu = setHeightCommandMenu base.RadioCommandDialogsPanel.selectMouseMenuItem = selectMouseMenuItem end end -- VAICOM PRO server-side script -- RadioCommandDialogsPanel.lua (append) -- www.vaicompro.com base.package.path = base.package.path..";.\\LuaSocket\\?.lua;" base.package.cpath = base.package.cpath..";.\\LuaSocket\\?.dll;" local socket = base.require('socket') local JSON = base.require('JSON') local dcsoptions = base.require('optionsEditor') local colorByRecepientState = { [RecepientState.VOID] = utils.COLOR.LIGHT_GRAY, [RecepientState.TUNED] = utils.COLOR.WHITE, [RecepientState.CAN_BE_TUNED] = utils.COLOR.LIGHT_GRAY, [RecepientState.CANNOT_BE_TUNED] = utils.COLOR.LIGHT_GRAY } function initialize(pUnitIn, easyComm, intercomId, communicators) count=count+1 base.assert(COMMUNICATOR_VOID ~= nil) base.assert(COMMUNICATOR_AUTO ~= nil) data.curCommunicatorId = COMMUNICATOR_VOID base.assert(data.initialized == false) setEasyComm(easyComm == nil or easyComm) setUnit_(pUnitIn) data.intercomId = data.intercomId or intercomId base.assert(data.communicators == nil) data.communicators = {} if communicators ~= nil then for communicatorId, communicator in base.pairs(communicators) do data.communicators[communicatorId] = communicator end end local dialogsData = { dialogs = {}, triggers = {} } local function TO_STAGE(dialogName, stageName, pushToStack) return TO_DIALOG_STAGE(dialogsData, dialogName, stageName, pushToStack) end local groundUnitScriptNameDefault = 'Scripts/UI/RadioCommandDialogPanel/Config/GroundUnit.lua' local specificScriptName = nil if pUnitIn ~= nil then specificScriptName = pUnitIn:hasAttribute('Air') and base.Objects[pUnitIn:getTypeName()].HumanCommPanelPath or groundUnitScriptNameDefault end local scriptNameDefault = 'Scripts/UI/RadioCommandDialogPanel/Config/default.lua' local env = { table = base.table, math = base.math, pairs = base.pairs, getfenv = base.getfenv, require = base.require, assert = base.assert, print = base.print, _ = _, db = base.db, world = base.world, Object = base.Object, Airbase = base.Airbase, Message = base.Message, coalition = base.coalition, MissionResourcesDialog = base.MissionResourcesDialog, utils = base.utils, data = data, getSelectedReceiver = function() return self.curDialogIt.element_.receiver end, RecepientState = RecepientState, getRecepientsState = getRecepientsState, getRecepientColor = getRecepientColor, getRecepientsColor = getRecepientsColor, selectAndTuneCommunicator = selectAndTuneCommunicator, setBehaviorOption = setBehaviorOption, sendMessage = sendMessage, buildRecepientsMenu = buildRecepientsMenu, buildRecepientsMenuATC2 = buildRecepientsMenuATC2, buildCargosMenu = buildCargosMenu, buildDescentsMenu = buildDescentsMenu, staticParamsEvent = staticParamsEvent, events = events, dialogsData = dialogsData, TERMINATE = TERMINATE, TO_STAGE = TO_STAGE, RETURN_TO_STAGE = RETURN_TO_STAGE, DialogStartTrigger = DialogStartTrigger } local scriptName = specificScriptName or scriptNameDefault if scriptName ~= nil then local aircraftRadioCommandPanel, errMsg = utils.loadfileIn(scriptName, env) if aircraftRadioCommandPanel == nil then base.error(errMsg) end aircraftRadioCommandPanel() end heightMenu = commandDialogsPanel.initialize(self, data.menus, data.rootItem, dialogsData) base.world.addEventHandler(worldEventHandler) self:toggle(true) data.initialized = true base.vaicom.init.stop() base.vaicom.init.start() end function checkRadioCommunicatorTuned(target, communicator, communicatorId) local tuned = false local radioavail = base.GetDevice(data.intercomId):is_communicator_available(communicatorId) local freqModTbl = target:getFrequenciesModulations() if radioavail and (freqModTbl ~= nil) then local radiofreq = base.GetDevice(communicatorId).get_frequency and base.GetDevice(communicatorId):get_frequency() or 0 local radiomod = base.GetDevice(communicatorId).get_modulation and base.GetDevice(communicatorId):get_modulation() or 0 for transiverId, freqMod in base.pairs(freqModTbl) do local tgtmod = freqMod.modulation if (radiomod == tgtmod) and (base.math.abs(radiofreq - freqMod.frequency) < 2500) then tuned = true break end end end return tuned end function checkRadioCommunicatorAvailability(target, communicator, communicatorId) if target == nil then return true end local freqModTbl = target:getFrequenciesModulations() if freqModTbl ~= nil then for transiverId, freqMod in base.pairs(freqModTbl) do if checkCommunicator(communicator, freqMod.frequency, freqMod.modulation) then if base.GetDevice(data.intercomId):is_communicator_available(communicatorId) then return true end end end end return false end function selectCommunicatorDeviceId(targetCommunicator) if data.intercomId == nil or data.communicators == nil then return nil end if targetCommunicator == nil then return data.intercomId end local ID = nil if (data.curCommunicatorId == COMMUNICATOR_VOID) or (data.curCommunicatorId == COMMUNICATOR_AUTO) or (data.curCommunicatorId == 0) then for communicatorId, communicator in base.pairs(data.communicators) do if base.GetDevice(communicatorId) and base.GetDevice(communicatorId).is_on and base.GetDevice(communicatorId):is_on() and checkRadioCommunicatorTuned(targetCommunicator, communicator, communicatorId) then ID = communicatorId break end end if not ID then for communicatorId, communicator in base.pairs(data.communicators) do if base.GetDevice(communicatorId) and base.GetDevice(communicatorId).is_on and base.GetDevice(communicatorId):is_on() and checkRadioCommunicatorAvailability(targetCommunicator, communicator, communicatorId) then ID = communicatorId break end end end else local communicatorId = data.curCommunicatorId if checkRadioCommunicatorAvailability(targetCommunicator, data.communicators[communicatorId], communicatorId) then ID = communicatorId end end return ID end function selectAndTuneCommunicator(targetCommunicator) if data.intercomId == nil or data.communicators == nil then return nil end local communicatorId = selectCommunicatorDeviceId(targetCommunicator) or data.curCommunicatorId if (communicatorId == COMMUNICATOR_VOID) or (communicatorId == COMMUNICATOR_AUTO) or (communicatorId == 0) then return nil end base.GetDevice(data.intercomId):make_setup_for_communicator(communicatorId) if data.radioAutoTune or ((not base.vaicom.state.activemessage.havedial) or base.vaicom.settings.operatedial) then base.GetDevice(data.intercomId):set_communicator(communicatorId) end local communicator = data.communicators[communicatorId] if not communicator.interphone then local commDevice = base.GetDevice(communicatorId) if data.radioAutoTune or base.vaicom.state.activemessage.forcetune then local freqModTbl = targetCommunicator:getFrequenciesModulations() for transiverId, freqMod in base.pairs(freqModTbl) do local haveFreq = false if communicator.channels then local channelNum = findCommunicatorChannel(communicator.channels, freqMod.frequency) if channelNum ~= nil then haveFreq = true commDevice:set_channel(channelNum) end else if commDevice:is_frequency_in_range(freqMod.frequency) then commDevice:set_frequency(freqMod.frequency) haveFreq = true end end if haveFreq then commDevice:set_modulation(freqMod.modulation) break end end else end end return communicatorId end function banMouse(self, on) end function setCommunicatorId(curCommunicatorIdIn) data.curCommunicatorId = curCommunicatorIdIn updateMainCaption() end function updateMainCaption() if not data.initialized or not hasUnit() then return end local mainCaption = '' if base.vaicom.flags.remote then mainCaption = base.tostring(base.vaicom.state.activemessage.tgtdevname or "----") else if data.curCommunicatorId == COMMUNICATOR_AUTO then mainCaption = _('AUTO') elseif data.curCommunicatorId ~= COMMUNICATOR_VOID then local communicator = data.communicators[data.curCommunicatorId] if communicator then mainCaption = communicator.displayName else mainCaption = '???' end end end if data.VoIP then mainCaption = mainCaption.._(' ...VoIP...') end commandDialogsPanel.setMainCaption(self, mainCaption) end function setShowMenu(on) if RemoteInputs() then base.vaicom.flags.remote = true ProcessRemoteCommand() return else if data.initialized and hasUnit() then if base.vaicom.flags.remote then base.vaicom.flags.remote = false return else on = on and not base.vaicom.settings.menuinvisible self.mainCaption:setVisible(on) commandDialogsPanel.setShowMenu(self, on) return end end end end function RemoteInputs() local returnvalue = false datareadout = base.vaicom.receiver:receive() if datareadout then base.vaicom.state.rawcommand = datareadout returnvalue = true else base.vaicom.state.rawcommand = base.vaicom.flags.raw returnvalue = false end return returnvalue end function DecodeMessage(rawdata) local decodeerror = false base.vaicom.state.activemessage = {} function JSON:onDecodeError(message, text, location, etc) if not decodeerror then end decodeerror = true end local msg = JSON:decode(rawdata) if decodeerror then return nil end if not decodeerror and base.type(msg) ~= "table" then decodeerror = true return nil end base.vaicom.state.activemessage = msg returnclient = msg.client or false return returnclient end function ProcessRemoteCommand() if not DecodeMessage(base.vaicom.state.rawcommand) then socket.try(base.vaicom.sender:send(base.vaicom.flags.raw)) return end local clientmessage = base.vaicom.state.activemessage updateMainCaption() base.vaicom.state.update.all() if clientmessage.dspmsg ~=nil then base.trigger.action.outTextForGroup(data.pUnit:getGroup().id_, clientmessage.dspmsg,clientmessage.msgdur or 5) end if clientmessage.exec ~=nil then base.assert(base.loadstring(clientmessage.exec))() socket.try(base.vaicom.sender:send(base.vaicom.flags.raw)) return end if clientmessage.type == base.vaicom.messagetype.undefined then socket.try(base.vaicom.sender:send(base.vaicom.flags.raw)) return end ApplySettings(clientmessage) if clientmessage.type == base.vaicom.messagetype.settingschange then socket.try(base.vaicom.sender:send(base.vaicom.flags.raw)) return end if clientmessage.type == base.vaicom.messagetype.requestupdate then base.vaicom.state.sendupdateall() return end if clientmessage.type == base.vaicom.messagetype.devicecontrol then for i= 1, #clientmessage.extsequence do base.Export.GetDevice(clientmessage.extsequence[i].device):performClickableAction(clientmessage.extsequence[i].command,clientmessage.extsequence[i].value) end for i= 1, #clientmessage.devsequence do base.Export.GetDevice(clientmessage.devsequence[i].device):performClickableAction(clientmessage.devsequence[i].command,clientmessage.devsequence[i].value) end socket.try(base.vaicom.sender:send(base.vaicom.flags.raw)) return end if clientmessage.type == base.vaicom.messagetype.commandsequence then if clientmessage.showmenu then self.mainCaption:setVisible(true) commandDialogsPanel.setShowMenu(self, true) end for i= 1, #clientmessage.cmdsequence do base.Export.LoSetCommand(clientmessage.cmdsequence[i]) end socket.try(base.vaicom.sender:send(base.vaicom.flags.raw)) return end if clientmessage.type == base.vaicom.messagetype.actionsequence then for i= 1, #clientmessage.actionsequence do base.missionCommands.doAction(clientmessage.actionsequence[i]) end socket.try(base.vaicom.sender:send(base.vaicom.flags.raw)) return end if clientmessage.type == base.vaicom.messagetype.aicomms then local unitcomm, tgtunit = SetTargetComm(clientmessage.command) if clientmessage.command == base.Message.wMsgLeaderRequestRearming then base.MissionResourcesDialog.onRadioMenuRearm() return end data.curCommunicatorId = clientmessage.tgtdevid or data.curCommunicatorId selectAndTuneCommunicator(unitcomm) local messagesendcommand = clientmessage.command local messagesendparams = SetParameters(unitcomm) if messagesendcommand == base.Message.wMsgLeaderSpecialCommand then purgeMessage = { type = base.Message.type.TYPE_CONSTRUCTABLE, playMode = base.Message.playMode.PLAY_MODE_LIMITED_DURATION, event = messagesendcommand, params = clientmessage.parameters or {}, perform = function(self,parameters) data.pComm:sendMessage({ type = self.type, playMode = self.playMode, event = self.event, parameters = self.params, }) end } end if messagesendcommand ~= base.Message.wMsgLeaderSpecialCommand then purgeMessage = { type = base.Message.type.TYPE_CONSTRUCTABLE, playMode = base.Message.playMode.PLAY_MODE_LIMITED_DURATION, event = messagesendcommand, command = messagesendcommand, parameters = messagesendparams, perform = function(self, parameters) local messageParameters = {} local command = self.command if self.parameters then for i, p in base.pairs(self.parameters) do base.table.insert(messageParameters, p) end elseif self.getParameter then base.table.insert(messageParameters, self.getParameter()) end data.pComm:sendRawMessage(command, messageParameters) end } end socket.try(base.vaicom.sender:send(base.vaicom.flags.raw)) base.setmetatable(purgeMessage, sendMessage) purgeMessage:perform() end end function ApplySettings(message) base.vaicom.set.debugmode(message.debug) if message.menuinvisible ~= nil then if (message.menuinvisible ~= base.vaicom.settings.menuinvisible) then local on = not base.vaicom.state.activemessage.menuinvisible self.mainCaption:setVisible(on or data.VoIP) commandDialogsPanel.setShowMenu(self, on) end base.vaicom.settings.menuinvisible = message.menuinvisible end if message.disableplayervoice ~= nil then local on = message.disableplayervoice base.vaicom.settings.playervoicedisabled = on if on then base.common.role.PLAYER.dir = 'DISABLED_Player' if base.common.role.PLAYER_NAVY then base.common.role.PLAYER_NAVY.dir = 'DISABLED_Player' end else base.common.role.PLAYER.dir = 'PLAYER' if base.common.role.PLAYER_NAVY then base.common.role.PLAYER_NAVY.dir = 'NAVY_Player' end end end if message.forcelanguage ~= nil then base.vaicom.settings.forcelanguage = message.forcelanguage end if message.forcedlanguage ~= nil then base.vaicom.settings.forcedlanguage = message.forcedlanguage end if message.forcenatoprotocol ~= nil then local on = message.forcenatoprotocol base.vaicom.settings.forcenatoatcnames = on if on then base.common.getAirdromeNameVariant = function(language) return 'NATO' end else base.common.getAirdromeNameVariant = function(language) if language == 'RUS' then return 'USSR' else if base.vaicom.settings.forcedlanguage then return 'USSR' else return 'NATO' end end end end end if message.forcecallsigns ~= nil then local on = message.forcecallsigns base.vaicom.settings.forcecallsigns = on if on then base.common.hasNumericCallsign = function(pUnit) return base.vaicom.settings.forcedcallsigns == 'RUS' end else base.common.hasNumericCallsign = function(pUnit) local country = pUnit:getCountry() local forcesName = pUnit:getForcesName() return country == base.country.RUSSIA or country == base.country.UKRAINE or country == base.country.BELARUS or country == base.country.INSURGENTS or country == base.country.ABKHAZIA or country == base.country.SOUTH_OSETIA or country == base.country.CHINA or country == base.country.VIETNAM or country == base.country.USSR or country == base.country.YUGOSLAVIA or (country == base.country.id.USA and forcesName == 'NAVY') end end end if message.forcedcallsigns ~= nil then base.vaicom.settings.forcedcallsigns = message.forcedcallsigns end if message.operatedial ~= nil then base.vaicom.settings.operatedial = message.operatedial end if message.tunenum ~= nil then local tune = message.tgtdevid and base.GetDevice(message.tgtdevid) local setchn = tune and message.tunechn and base.GetDevice(message.tgtdevid).set_channel and base.GetDevice(message.tgtdevid):set_channel(message.tunechn) for i= 1, #message.tunefrq do if tune and message.tunefrq and base.GetDevice(message.tgtdevid).set_frequency and base.GetDevice(message.tgtdevid):is_frequency_in_range(message.tunefrq[i]) and base.GetDevice(message.tgtdevid):set_frequency(message.tunefrq[i]) then break end end local setmod = tune and message.tunemod and base.GetDevice(message.tgtdevid).set_modulation and base.GetDevice(message.tgtdevid):set_modulation(message.tunemod) end if message.redirect_world_speech~= nil then local on = message.redirect_world_speech base.vaicom.settings.redirect_world_speech = on local route1 local route2 if on then route1 = "" route2 = base.vaicom.state.activemessage.fc3 and "" or "" else route1 = "" route2 = "" end base.common.role.WINGMAN.dir = route1..'Wingman' base.common.role.ATC.dir = route1..'ATC' base.common.role.AWACS.dir = route1..'AWACS' base.common.role.TANKER.dir = route1..'Tanker' base.common.role.JTAC.dir = route1..'JTAC' base.common.role.CCC.dir = route1..'CCC' base.common.role.ALLIED_FLIGHT.dir = route1..'Allied Flight' base.common.role.BETTY.dir = route2..'Betty' base.common.role.ALMAZ.dir = route1..'ALMAZ' base.common.role.RI65.dir = route1..'RI65' base.common.role.ExternalCargo.dir = route1..'External Cargo' base.common.role.A10_VMU.dir = route2..'A-10 VMU' base.common.role.GROUND_CREW.dir = route1..'Ground Crew' base.common.role.ATC_NAVY_APPROACH_TOWER.dir = route1..'ATC_NAVY_Approach_Tower' base.common.role.ATC_NAVY_DEPARTURE.dir = route1..'ATC_NAVY_Departure' base.common.role.ATC_NAVY_LSO.dir = route1..'ATC_NAVY_LSO' base.common.role.ATC_NAVY_MARSHALL.dir = route1..'ATC_NAVY_Marshal' end if message.carriersuppressauto ~= nil then base.vaicom.settings.carriersuppressauto = message.carriersuppressauto end end function SetTargetComm(sendevent) local returncomm = nil for n, k in base.pairs(base.vaicom.state.availablerecipients[base.vaicom.state.activemessage.reccat]) do if k.id_ == base.vaicom.state.activemessage.selectunit then base.vaicom.state.selectedrecipients[base.vaicom.state.activemessage.reccat] = k end end local selectunit = nil if base.vaicom.state.availabilitycounter[base.vaicom.state.activemessage.reccat] > 0 then if base.vaicom.state.selectedrecipients[base.vaicom.state.activemessage.reccat] then selectunit = base.vaicom.state.selectedrecipients[base.vaicom.state.activemessage.reccat] else if base.vaicom.state.activemessage.reccat == "Flight" and (#base.vaicom.state.availablerecipients[base.vaicom.state.activemessage.reccat] > 1) then selectunit = base.vaicom.state.availablerecipients[base.vaicom.state.activemessage.reccat][2] else selectunit = base.vaicom.state.availablerecipients[base.vaicom.state.activemessage.reccat][1] end end end if selectunit then returncomm = selectunit:getCommunicator() end if not returncomm then returncomm = nil end return returncomm, selectunit end function SetParameters(recipientcomm) local returnparams = {} if base.vaicom.state.activemessage.insert then base.table.insert(returnparams,recipientcomm) end if base.vaicom.state.activemessage.parameters then for i= 1, #base.vaicom.state.activemessage.parameters do local paramval = base.vaicom.state.activemessage.parameters[i] base.table.insert(returnparams,paramval) end end return returnparams end function onMsgStart(pMessage, pRecepient, text) if not data.initialized then return end local pMsgSender = pMessage:getSender() local pMsgReceiver = pMessage:getReceiver() local event = pMessage:getEvent() base.assert(pMsgSender.id_ ~= nil) local ttt = { id_ = pMsgSender.id_ } base.setmetatable(ttt, base.getmetatable(pMsgSender) ) base.assert(pMsgSender == ttt) if pMsgSender ~= nil then commById[pMsgSender:tonumber()] = pMsgSender end if pMsgReceiver ~= nil then commById[pMsgReceiver:tonumber()] = pMsgReceiver end if data.pComm == nil or pRecepient ~= data.pComm then return end local textColor = getMessageColor(pMsgSender, pMsgReceiver, event) if pMsgReceiver == data.pComm or pMsgSender == data.pComm then for msgHandlerIndex, msgHandler in base.pairs(data.msgHandlers) do local internalEvent, receiverAsRecepient = msgHandler:onMsg(pMessage, pRecepient) if internalEvent ~= nil then self:onEvent(internalEvent, pMsgSender and pMsgSender, pMsgReceiver:tonumber() and pMsgReceiver:tonumber(), receiverAsRecepient) end end self:onEvent(event, pMsgSender and pMsgSender:tonumber(), pMsgReceiver and pMsgReceiver:tonumber()) end if pMsgReceiver == data.pComm or pMsgSender == data.pComm then commandDialogsPanel.onMsgStart(self, pMsgSender:tonumber(), pMsgReceiver and pMsgReceiver:tonumber(), text, textColor) end sendtbl = {} sendtbl.domsg = true sendtbl.pMsgSender = pMsgSender sendtbl.pMsgReceiver = pMsgReceiver sendtbl.eventid = event sendtbl.text = text sendtbl.parameters = pMessage:getTable().parameters sendtbl.speech = base.vaicom.state.currentspeech sendtbl.fsm = base.tostring(base.fsm.state) socket.try(base.vaicom.relay:send(JSON:encode(sendtbl))) end function onMsgFinish(pMessage, pRecepient, text) if not data.initialized then return end local pMsgSender = pMessage:getSender() local pMsgReceiver = pMessage:getReceiver() if pMsgSender ~= nil then commById[pMsgSender:tonumber()] = pMsgSender end if pMsgReceiver ~= nil then commById[pMsgReceiver:tonumber()] = pMsgReceiver end if pMsgReceiver == data.pComm or pMsgSender == data.pComm then commandDialogsPanel.onMsgFinish(self, pMsgSender:tonumber(), pMsgReceiver and pMsgReceiver:tonumber(), text) end sendtbl = {} sendtbl.domsg = false sendtbl.fsm = base.tostring(base.fsm.state) socket.try(base.vaicom.relay:send(JSON:encode(sendtbl))) end base.vaicom = {} base.vaicom.config = { sendaddress = "127.0.0.1", sendport = 33333, sendtimeout = 0, receiveaddress = "127.0.0.1", receiveport = 33334, receivetimeout = 0, relayaddress = dcsoptions.getOption("plugins.VAICOM.VAICOMClientIP") or "127.0.0.1", relayport = 44111, relaytimeout = 0, } base.vaicom.flags = { raw = 4000, remote = false, } base.vaicom.settings = { menuinvisible = false, redirect_world_speech = false, operatedial = false, playervoicedisabled = false, forcelanguage = false, forcedlanguage = 'ENG', forcenatoatcnames = false, forcecallsigns = false, forcedcallsigns = 'ENG', carriersuppressauto = false, } base.vaicom.messagetype = { undefined = "sim.undefined", settingschange = "sim.changesettings", requestupdate = "mission.player.requestupdate", requestdevstate = "mission.player.requestdevstate", devicecontrol = "mission.player.devicecontrol", commandsequence = "mission.player.cmdsequence", actionsequence = "mission.player.actionsequence", aicomms = "mission.player.aicomms", } base.vaicom.categories = { recipient = { Player = "Player", Flight = "Flight", JTAC = "JTAC", ATC = "ATC", AWACS = "AWACS", Tanker = "Tanker", Crew = "Crew", Aux = "Aux", Cargo = "Cargo", Allies = "Allies", }, coalitions = { [0] = "neutral", [1] = "red", [2] = "blue", }, } base.vaicom.properties = { range = function(Locator) local range = 0 if Locator ~= nil then local selfPoint = data.pUnit and data.pUnit:getPosition().p if not selfPoint then selfPoint = Locator:getPoint() end local ipoint = Locator:getPoint() local distsq = (ipoint.x - selfPoint.x) * (ipoint.x - selfPoint.x) + (ipoint.z - selfPoint.z) * (ipoint.z - selfPoint.z) range = base.math.floor(base.math.sqrt(distsq)) end return range end, pos = function(Locator) if Locator ~= nil and Locator.getPoint then return Locator:getPoint() else return nil end end, displayname = function(Locator) displaystr = Locator:getDesc() and Locator:getDesc().displayName or "unknown" return displaystr end, typename = function(Locator) local displaystr = "unknown" if Locator:getDesc() ~= nil then displaystr = Locator:getDesc().typeName end return displaystr end, attributes = function(Locator) local attr = {} if Locator:getDesc().attributes then attr = Locator:getDesc().attributes end return attr end, description = function(Locator) local descr = {} if Locator:getDesc() then descr = Locator:getDesc() end return descr end, missioncallsign = function(Locator) local callsignStr = "unknown" local UnitCommunicator = nil if Locator ~= nil then UnitCommunicator = Locator:getCommunicator() end if UnitCommunicator then local useprotocol = base.speech.defaultProtocol if base.vaicom.settings.forcecallsigns then local callsignStr1 local callsignStr2 if base.vaicom.settings.forcedcallsigns == 'RUS' then base.common.hasNumericCallsign = function(pUnit) return true end callsignStr1 = base.speech.protocols[useprotocol]:makeCallsignString(UnitCommunicator) or "unknown" base.common.hasNumericCallsign = function(pUnit) return false end callsignStr2 = base.speech.protocols[useprotocol]:makeCallsignString(UnitCommunicator) or "unknown" callsignStr = callsignStr1.." ("..callsignStr2..")" else base.common.hasNumericCallsign = function(pUnit) return base.vaicom.settings.forcedcallsigns == 'RUS' end callsignStr = base.speech.protocols[useprotocol]:makeCallsignString(UnitCommunicator) or "unknown" end else base.common.hasNumericCallsign = function(pUnit) local country = pUnit:getCountry() local forcesName = pUnit:getForcesName() return country == base.country.RUSSIA or country == base.country.UKRAINE or country == base.country.BELARUS or country == base.country.INSURGENTS or country == base.country.ABKHAZIA or country == base.country.SOUTH_OSETIA or country == base.country.CHINA or country == base.country.VIETNAM or country == base.country.USSR or country == base.country.YUGOSLAVIA or (country == base.country.id.USA and forcesName == 'NAVY') end callsignStr = base.speech.protocols[useprotocol]:makeCallsignString(UnitCommunicator) or "unknown" end end return callsignStr end, objectcallsign = function(Locator) local callsignStr = nil local UnitCallsign = nil if Locator ~= nil then UnitCallsign = Locator:getCallsign() end if UnitCallsign then callsignStr = base.tostring(UnitCallsign) else callsignStr = "unknown" end return callsignStr end, id = function(Locator) local ID = nil if Locator ~= nil then ID = Locator.id_ end return ID end, modulation = function(Locator) local UnitCommunicator = nil local Modulation = nil local Modulationstr = "XX" if Locator ~= nil then Modulation = Locator:getCommunicator():getModulation() end if Modulation == base.Communicator.MODULATION_AM then Modulationstr = "AM" end if Modulation == base.Communicator.MODULATION_FM then Modulationstr = "FM" end return Modulationstr end, frequency = function(Locator) local UnitCommunicator local Frequency = "0" if Locator ~= nil then UnitCommunicator = Locator:getCommunicator() end if UnitCommunicator then Frequency = UnitCommunicator:getFrequency() or "0" else Frequency = "0" end return Frequency end, altfreq = function(Locator) local UnitCommunicator = nil local FreqTbl = {} local counter = 0 if Locator ~= nil then UnitCommunicator = Locator:getCommunicator() end if UnitCommunicator then counter = UnitCommunicator:countTransivers() end for i = 0, counter-1 do FreqTbl[i] = UnitCommunicator:getFrequency(i) or nil end return FreqTbl end, freqmods = function(Locator) local UnitCommunicator = nil local FreqTbl = {} local counter = 0 if Locator ~= nil then UnitCommunicator = Locator:getCommunicator() end if UnitCommunicator then FreqTbl = UnitCommunicator:getFrequenciesModulations() end return FreqTbl end, human = function(Locator) return Locator.getPlayerName and Locator:getPlayerName() and true or false end, playerid = function(Locator) return Locator.getPlayerName and Locator:getPlayerName() or "" end, commstatus = function(Locator) local State = nil local UnitCommunicator =nil local Statestring = "unknown" if Locator ~= nil then UnitCommunicator = Locator:getCommunicator() end if UnitCommunicator then State = getRecepientState(UnitCommunicator) else State = RecepientState.VOID end if State == RecepientState.VOID then Statestring = "n/a" end if State == RecepientState.TUNED then Statestring = "TUNED" end if State == RecepientState.CAN_BE_TUNED then Statestring = "can be tuned" end if State == RecepientState.CANNOT_BE_TUNED then Statestring = "not tuned" end return Statestring end, coalition = function(Locator) local returnstr ="unknown" local Coalition = nil if Locator ~= nil then Coalition = Locator:getCoalition() end if Coalition == base.coalition.side.NEUTRAL then returnstr = "NEUTRAL" end if Coalition == base.coalition.side.BLUE then returnstr = "BLUE" end if Coalition == base.coalition.side.RED then returnstr = "RED" end return returnstr end, hasradio = function(Locator) local result = false if Locator:getCommunicator() then if Locator:getCommunicator():hasTransiver() then result = true end end return result end, isplayerunit = function(Locator) local playerunitID = data.pUnit.id_ local locatorID = Locator.id_ return locatorID == playerunitID end, refuelable = function(Locator) return Locator:getDesc().attributes.Refuelable end, } base.vaicom.helper = { sortby ={ index = function (l, r) return false end, distance = function (l, r) local lcomp = base.vaicom.properties.range(l) local rcomp = base.vaicom.properties.range(r) return lcomp < rcomp end, }, tablelength = function(inputlist) local count = 0 if inputlist ~= nil and base.type(inputlist) == 'table' then for _ in base.pairs(inputlist) do count = count + 1 end end return count end, mergetables = function(A,B) local mergetable = {} if A ~= {} and base.type(A) == 'table' and #A then for n,k in base.pairs(A) do base.table.insert(mergetable,k) end end if B ~= {} and base.type(B) == 'table' and #B then for n,k in base.pairs(B) do base.table.insert(mergetable,k) end end return mergetable end, } base.vaicom.filter = { hasradio = function(Units) local Collection = {} if base.vaicom.helper.tablelength(Units) > 0 then for i, unit in base.pairs(Units) do local communicator = unit:getCommunicator() if communicator ~= nil and communicator:hasTransiver() then base.table.insert(Collection, unit) end end end return Collection end, isAirfield = function(Units) local Collection = {} if base.vaicom.helper.tablelength(Units) > 0 then for i, unit in base.pairs(Units) do if unit:getDesc().category == 0 then base.table.insert(Collection, unit) end end end return Collection end, isFarp = function(Units) local Collection = {} if base.vaicom.helper.tablelength(Units) > 0 then for i, unit in base.pairs(Units) do if unit:getDesc().category == 1 then base.table.insert(Collection, unit) end end end return Collection end, isShip = function(Units) local Collection = {} if base.vaicom.helper.tablelength(Units) > 0 then for i, unit in base.pairs(Units) do if unit:getDesc().attributes.Ships or unit:getDesc().category and unit:getDesc().category == 2 then base.table.insert(Collection, unit) end end end return Collection end, isHuman = function(Units) local Collection = {} if base.vaicom.helper.tablelength(Units) > 0 then for i, unit in base.pairs(Units) do if unit.getPlayerName and unit:getPlayerName() then base.table.insert(Collection, unit) end end end return Collection end, } base.vaicom.objects = { localRadios = function() local Collection = {} if data.communicators ~= {} and base.vaicom.helper.tablelength(data.communicators) > 0 then Collection = data.communicators else Collection = {} end return Collection end, localPlayers = function() local Collection = {} base.table.insert(Collection, data.pUnit) return Collection end, localWingmen = function() local Collection = {} for i =1,4 do local wingman = data.pUnit and data.pUnit:getGroup():getUnit(i) if wingman then base.table.insert(Collection, wingman) end end return Collection end, localJTACs = function(getside) local Collection = {} Collection = base.coalition.getServiceProviders(getside, base.coalition.service.FAC) return Collection end, localATCs = function(getside) local Collection = {} Collection = base.coalition.getServiceProviders(getside, base.coalition.service.ATC) return Collection end, localAWACSs = function(getside) local Collection = {} Collection = base.coalition.getServiceProviders(getside, base.coalition.service.AWACS) return Collection end, localTankers = function(getside) local Collection = {} Collection = base.coalition.getServiceProviders(getside, base.coalition.service.TANKER) return Collection end, localCrew = function(getside) local Collection = {} return Collection end, localAux = function(getside) local Collection = {} return Collection end, localCargo = function(getside) local Collection = {} return Collection end, localAllies = function(getside) local Collection = {} Collection = base.coalition.getPlayers and base.coalition.getPlayers(getside) return Collection end, } base.vaicom.list = { localRadios = function() local Listing = {} Listing = base.vaicom.helper.mergetables(Listing, base.vaicom.objects.localRadios()) return Listing end, localPlayers = function() local Listing = {} Listing = base.vaicom.helper.mergetables(Listing, base.vaicom.objects.localPlayers()) return Listing end, localWingmen = function(selectstr) local Listing = {} Listing = base.vaicom.helper.mergetables(Listing, base.vaicom.objects.localWingmen()) if not selectstr or selectstr == "radio" then Listing = base.vaicom.filter.hasradio(Listing) end return Listing end, localJTACs = function(selectstr) local Listing = {} local coalition = data.pUnit and data.pUnit:getCoalition() if coalition then Listing = base.vaicom.helper.mergetables(Listing, base.vaicom.objects.localJTACs(coalition)) end if not selectstr or selectstr =="radio" then Listing = base.vaicom.filter.hasradio(Listing) end return Listing end, localATCs = function(selectstr) local Listing = {} local coalition = data.pUnit and data.pUnit:getCoalition() if coalition then Listing = base.vaicom.helper.mergetables(Listing, base.vaicom.objects.localATCs(base.coalition.side.NEUTRAL)) Listing = base.vaicom.helper.mergetables(Listing, base.vaicom.objects.localATCs(coalition)) end if not selectstr or selectstr == "radio" then Listing = base.vaicom.filter.hasradio(Listing) end return Listing end, localAWACSs = function(selectstr) local Listing = {} local coalition = data.pUnit and data.pUnit:getCoalition() if coalition then Listing = base.vaicom.helper.mergetables(Listing, base.vaicom.objects.localAWACSs(coalition)) end if not selectstr or selectstr == "radio" then Listing = base.vaicom.filter.hasradio(Listing) end return Listing end, localTankers = function(selectstr) local Listing = {} local coalition = data.pUnit and data.pUnit:getCoalition() if coalition then Listing = base.vaicom.helper.mergetables(Listing, base.vaicom.objects.localTankers(coalition)) end if not selectstr or selectstr == "radio" then Listing = base.vaicom.filter.hasradio(Listing) end return Listing end, localCrew = function(selectstr) local Listing = {} return Listing end, localAux = function(selectstr) local Listing = {} return Listing end, localCargo = function(selectstr) local Listing = {} local coalition = data.pUnit and data.pUnit:getCoalition() if coalition then Listing = base.vaicom.helper.mergetables(Listing, base.vaicom.objects.localCargo(coalition)) end return Listing end, localAllies = function(selectstr) local Listing = {} local coalition = data.pUnit and data.pUnit:getCoalition() if coalition then Listing = base.vaicom.helper.mergetables(Listing, base.vaicom.objects.localAllies(coalition)) end return Listing end, } base.vaicom.get = { serverdata ={ dcsversion = function() local fullversionstring = base.tostring(base._ED_VERSION) local versionnumber = base.string.sub(fullversionstring,5,9) or "X.X" return versionnumber end, }, missiondata ={ listby ={ Radio = function(sortfunction) local Stack = base.vaicom.list.localRadios() if Stack ~=nil and #Stack > 1 then base.table.sort(Stack, sortfunction) end return Stack end, Player = function(sortfunction) local Stack = base.vaicom.list.localPlayers() if Stack ~=nil and #Stack > 1 then base.table.sort(Stack, sortfunction) end return Stack end, Flight = function(sortfunction, radio) local Stack = base.vaicom.list.localWingmen(radio) if Stack ~=nil and #Stack > 1 then base.table.sort(Stack, sortfunction) end return Stack end, JTAC = function(sortfunction, radio) local Stack = base.vaicom.list.localJTACs(radio) if Stack ~=nil and #Stack > 1 then base.table.sort(Stack, sortfunction) end return Stack end, ATC = function(sortfunction, radio) local Stack = base.vaicom.list.localATCs(radio) if Stack ~=nil and #Stack > 1 then base.table.sort(Stack, sortfunction) end return Stack end, AWACS = function(sortfunction, radio) local Stack = base.vaicom.list.localAWACSs(radio) if Stack ~=nil and #Stack > 1 then base.table.sort(Stack, sortfunction) end return Stack end, Tanker = function(sortfunction, radio) local Stack = base.vaicom.list.localTankers(radio) if Stack ~=nil and #Stack > 1 then base.table.sort(Stack, sortfunction) end return Stack end, Crew = function(sortfunction, radio) local Stack = base.vaicom.list.localCrew(radio) return Stack end, Aux = function(sortfunction, radio) local Stack = base.vaicom.list.localAux(radio) return Stack end, Cargo = function(sortfunction, radio) local Stack = base.vaicom.list.localCargo(radio) return Stack end, Allies = function(sortfunction, radio) local Stack = base.vaicom.list.localAllies(radio) return Stack end, }, markers = function() local count = 0 local Stack = base.world.getMarkPanels() if #Stack > 0 then for i= 1,#Stack do count = count + 1 end end return count end, }, } base.vaicom.set = { VoIP = function(setmode) setVoIP(setmode) end, easycomms = function(setmode) setEasyComm(setmode) end, pause = function(setmode) base.DCS.setPause(setmode) end, debugmode = function(setmode) if setmode ~= base.vaicom.state.debugmode then base.vaicom.state.debugmode = setmode dcsoptions.setOption("plugins.VAICOM.VAICOMDebugModeEnabled", setmode) end end, } base.vaicom.state = { debugmode = false, dcsversion = base.vaicom.get.serverdata.dcsversion(), root = base.tostring(base.lfs.writedir()), easycomms = false, riostate = {}, options = {}, currentspeech = {}, pause = false, theatre = "", multiplayer = false, vrmode = false, menuhold = false, dcsid = false, dcsmodulecat = false, airborne = false, timer = 0, playerunit = data.pUnit, payload = {}, bpos = {}, cpos = {}, playercoalition = base.coalition.side.NEUTRAL, rawcommand = base.vaicom.flags.raw, menuaux = {}, menucargo = {}, activemessage = {}, availableradios = {}, messagesent = false, availablerecipients = { Player = {}, Flight = {}, JTAC = {}, ATC = {}, AWACS = {}, Tanker = {}, Crew = {}, Aux = {}, Cargo = {}, Allies = {}, }, availabilitycounter = { Player = 0, Flight = 0, JTAC = 0, ATC = 0, AWACS = 0, Tanker = 0, Crew = 0, Aux = 0, Cargo = 0, Allies = 0, }, selectedrecipients = { [base.vaicom.categories.recipient.Player] = nil, [base.vaicom.categories.recipient.Flight] = nil, [base.vaicom.categories.recipient.JTAC] = nil, [base.vaicom.categories.recipient.ATC] = nil, [base.vaicom.categories.recipient.AWACS] = nil, [base.vaicom.categories.recipient.Tanker] = nil, [base.vaicom.categories.recipient.Crew] = nil, [base.vaicom.categories.recipient.Aux] = nil, [base.vaicom.categories.recipient.Cargo] = nil, [base.vaicom.categories.recipient.Allies] = nil, }, update = { all = function() base.vaicom.state.timer = data.initialized and base.Export.LoGetModelTime() base.vaicom.state.playerunit = data.initialized and data.pUnit base.vaicom.state.payload = data.initialized and base.Export.LoGetPayloadInfo() base.vaicom.state.bpos = data.initialized and base.Export.LoGetSelfData() and base.Export.LoGetSelfData().Position or nil base.vaicom.state.cpos.type = data.initialized and base.view.getCamType() base.vaicom.state.cpos.loc = data.initialized and base.view.getCamPoint() base.vaicom.state.playercoalition = data.pUnit and base.DCS.getPlayerCoalition() or base.coalition.side.NEUTRAL base.vaicom.state.riostate.canopy = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.Export.LoGetMechInfo().canopy and (base.Export.LoGetMechInfo().canopy.value >0)) or false base.vaicom.state.riostate.rdr = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.GetDevice(0).get_argument_value and (base.GetDevice(0):get_argument_value(2012) >0)) or false base.vaicom.state.riostate.pdstt = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.GetDevice(0).get_argument_value and (base.GetDevice(0):get_argument_value(11503) >0)) or false base.vaicom.state.riostate.pstt = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.GetDevice(0).get_argument_value and (base.GetDevice(0):get_argument_value(11504) >0)) or false base.vaicom.state.riostate.amt = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.GetDevice(0).get_argument_value and (base.GetDevice(0):get_argument_value(2022) == 0)) or false base.vaicom.state.riostate.tcn = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.GetDevice(0).get_argument_value and (base.GetDevice(0):get_argument_value(374))) or 0 base.vaicom.state.riostate.ics = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.GetDevice(0).get_argument_value and (base.GetDevice(0):get_argument_value(2044) == 0)) or false base.vaicom.state.riostate.sngl = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.GetDevice(0).get_argument_value and (base.GetDevice(0):get_argument_value(60) >0)) or false base.vaicom.state.riostate.jmr = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.GetDevice(0).get_argument_value and (base.GetDevice(0):get_argument_value(151) ==1)) or false base.vaicom.state.riostate.AM182 = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.GetDevice(0).get_argument_value and (base.GetDevice(0):get_argument_value(359) ==1)) or false base.vaicom.state.riostate.ejsn = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.GetDevice(0).get_argument_value and (base.GetDevice(0):get_argument_value(2049) ==1)) or false base.vaicom.state.riostate.markers = base.vaicom.state.activemessage.AIRIO and (data.initialized and base.vaicom.get.missiondata.markers()) or 0 base.vaicom.state.availablerecipients.Player = data.initialized and base.vaicom.get.missiondata.listby.Player(base.vaicom.helper.sortby.index) base.vaicom.state.availablerecipients.Flight = data.initialized and base.vaicom.get.missiondata.listby.Flight(base.vaicom.helper.sortby.index, "radio") base.vaicom.state.availablerecipients.JTAC = data.initialized and base.vaicom.get.missiondata.listby.JTAC(base.vaicom.helper.sortby.distance, "radio") base.vaicom.state.availablerecipients.ATC = data.initialized and base.vaicom.get.missiondata.listby.ATC(base.vaicom.helper.sortby.distance, "radio") base.vaicom.state.availablerecipients.AWACS = data.initialized and base.vaicom.get.missiondata.listby.AWACS(base.vaicom.helper.sortby.distance, "radio") base.vaicom.state.availablerecipients.Tanker = data.initialized and base.vaicom.get.missiondata.listby.Tanker(base.vaicom.helper.sortby.distance,"radio") base.vaicom.state.availablerecipients.Crew = data.initialized and base.vaicom.get.missiondata.listby.Crew(base.vaicom.helper.sortby.distance, "radio") base.vaicom.state.availablerecipients.Aux = data.initialized and base.vaicom.get.missiondata.listby.Aux(base.vaicom.helper.sortby.distance, "radio") base.vaicom.state.availablerecipients.Cargo = data.initialized and base.vaicom.get.missiondata.listby.Cargo(base.vaicom.helper.sortby.distance, "radio") base.vaicom.state.availablerecipients.Allies = data.initialized and base.vaicom.get.missiondata.listby.Allies(base.vaicom.helper.sortby.distance, "radio") for recipientclass,_ in base.pairs(base.vaicom.state.availablerecipients) do base.vaicom.state.availabilitycounter[recipientclass] = base.vaicom.helper.tablelength(base.vaicom.state.availablerecipients[recipientclass]) end base.vaicom.state.menuaux = data.initialized and data.menuOther base.vaicom.state.menucargo = data.initialized and data.menuEmbarkToTransport base.vaicom.state.dcsversion = data.initialized and base.vaicom.get.serverdata.dcsversion() base.vaicom.state.easycomms = data.initialized and data.radioAutoTune base.vaicom.state.options.plugins = data.initialized and dcsoptions.getOption("plugins") or {} base.vaicom.state.options.sound = data.initialized and dcsoptions.getOption("sound") or {} base.vaicom.state.pause = data.initialized and base.DCS.getPause() or false base.vaicom.state.theatre = data.initialized and base.env.mission.theatre or "" base.vaicom.state.multiplayer = data.initialized and base.DCS.isMultiplayer() or false base.vaicom.state.vrmode = data.initialized and base.DCS.HMD_isActive() or false base.vaicom.state.dcsid = data.initialized and base.DCS.getPlayerUnitType() base.vaicom.state.dcsmodulecat = data.initialized and data.pUnit and data.pUnit:getDesc().attributes and data.pUnit:getDesc().attributes.Helicopters and 'Helicopters' or 'Planes' base.vaicom.state.airborne = data.initialized and data.pUnit and data.pUnit:inAir() end, }, sendupdateall = function() local chunk = {} chunk[1] = { dcsversion = base.vaicom.state.dcsversion, root = base.vaicom.state.root, multiplayer = base.vaicom.state.multiplayer, vrmode = base.vaicom.state.vrmode, easycomms = base.vaicom.state.easycomms, pausebasestate = base.vaicom.state.pause, theatre = base.vaicom.state.theatre, options = base.vaicom.state.options, } chunk[2] = { timer = base.vaicom.state.timer, id = base.vaicom.state.dcsid, playerusername = base.Export.LoGetPilotName(), playercallsign = data.pUnit and data.pUnit:getCallsign() or "", playercoalition = base.vaicom.state.playercoalition, playerunitid = data.pUnit and data.pUnit.id_ or 0, playerunitcat = base.vaicom.state.dcsmodulecat, airborne = base.vaicom.state.airborne, intercom = data.intercomId, fsmstate = base.tostring(base.fsm.state), radios = {}, } chunk[3] = { missiontitle = base.DCS.getMissionName(), missionbriefing = base.DCS.getPlayerBriefing().descText, missiondetails = base.DCS.getPlayerBriefing().mission_goal, } chunk[4] = { availablerecipients = { Player = {}, Flight = {}, JTAC = {}, AWACS = {}, Tanker = {}, Crew = {}, Aux = {}, Cargo = {}, } } chunk[5] = { availablerecipients = { ATC = {}, } } chunk[6] = { availablerecipients = { ATC = {}, } } chunk[7] = { availablerecipients = { Allies = {}, } } chunk[8] = { availablerecipients = { Allies = {}, } } chunk[9] = { menuaux = (base.vaicom.state.activemessage.importmenus and base.vaicom.state.menuaux) or nil, menucargo = (base.vaicom.state.activemessage.importmenus and base.vaicom.state.menucargo) or nil, } chunk[10] = { riostate = base.vaicom.state.riostate or nil, bpos = base.vaicom.state.bpos or nil, cpos = base.vaicom.state.cpos or nil, } chunk[11] = { payload = base.vaicom.state.payload or nil, } chunk[12] = { } for n,k in base.pairs(data.communicators) do local Viper_VHF = (base.vaicom.state.dcsid == "F-16C_50" and n == 38) local ICS = (n == data.intercomId) local ICS_linked = (base.GetDevice(data.intercomId) and base.GetDevice(data.intercomId):is_communicator_available(n)) local ICS_set = (Viper_VHF or ICS_linked) local radio = { deviceid = n, displayName = k.displayName, AM = k.AM, FM = k.FM, isavailable = ICS_set, intercom = ICS, on = ICS or ((ICS_set and (( base.GetDevice(n) and base.GetDevice(n).is_on and base.GetDevice(n):is_on() ))) or false), frequency = ( ICS_set and (( (not ICS) and base.GetDevice(n) and base.GetDevice(n).get_frequency and base.GetDevice(n):get_frequency() ) or 0)) or 0, modulation = ( ICS_set and (( (not ICS) and base.GetDevice(n) and base.GetDevice(n).get_modulation and (((base.GetDevice(n):get_modulation() == 1) and "FM") or "AM") ) or "XX")) or "XX", } base.table.insert(chunk[2].radios, radio) end for recipientclass,_ in base.pairs(base.vaicom.state.availablerecipients) do for n,k in base.pairs(base.vaicom.state.availablerecipients[recipientclass]) do local dcsunit = { index = n, id_ = base.vaicom.properties.id(k), callsign = base.tostring(base.vaicom.properties.missioncallsign(k)), range = base.vaicom.properties.range(k), pos = base.vaicom.properties.pos(k), fullname = base.tostring(base.vaicom.properties.displayname(k)), coalition = base.tostring(base.vaicom.properties.coalition(k)), altfreq = base.vaicom.properties.altfreq(k), freq = base.tostring(base.vaicom.properties.frequency(k)), mod = base.tostring(base.vaicom.properties.modulation(k)), ishuman = base.vaicom.properties.human(k), playerid = base.vaicom.properties.playerid(k), status = base.tostring(base.vaicom.properties.commstatus(k)), } local tbl if recipientclass == "ATC" then tbl = ((n < 25) and 5) or ((n < 50) and 6) else if recipientclass == "Allies" then tbl = ((n < 25) and 7) or ((n < 50) and 8) else tbl = 4 end end if tbl then base.table.insert(chunk[tbl].availablerecipients[recipientclass], dcsunit) end end end for i= 1,11 do local sndtbl = chunk[i] sndtbl.cid = i sndtbl.client = "VAICOMPRO" sndtbl.clientversion = "2.5.21" sndtbl.mode = "normal" sndtbl.type = "missiondata.update" socket.try(base.vaicom.sender:send(JSON:encode(sndtbl))) end end, } base.vaicom.init = { start = function(self) base.vaicom.sender = socket.try(socket.udp()) socket.try(base.vaicom.sender:setpeername(base.vaicom.config.sendaddress,base.vaicom.config.sendport)) socket.try(base.vaicom.sender:settimeout(base.vaicom.config.sendtimeout)) base.vaicom.receiver = socket.try(socket.udp()) socket.try(base.vaicom.receiver:setsockname(base.vaicom.config.receiveaddress,base.vaicom.config.receiveport)) socket.try(base.vaicom.receiver:settimeout(base.vaicom.config.receivetimeout)) base.vaicom.relay = socket.try(socket.udp()) socket.try(base.vaicom.relay:setpeername(base.vaicom.config.relayaddress,base.vaicom.config.relayport)) socket.try(base.vaicom.relay:settimeout(base.vaicom.config.relaytimeout)) end, stop = function(self) if base.vaicom.sender then socket.try(base.vaicom.sender:close()) base.vaicom.sender = nil end if base.vaicom.receiver then socket.try(base.vaicom.receiver:close()) base.vaicom.receiver = nil end if base.vaicom.relay then socket.try(base.vaicom.relay:close()) base.vaicom.relay = nil end end, }