------------------------------------------------------------------ -- VEAF radio menu script library for DCS World -- By zip (2018) -- -- Features: -- --------- -- * Manage the VEAF radio menus in the F10 - Other menu -- -- See the documentation : https://veaf.github.io/documentation/ ------------------------------------------------------------------ --- veafRadio Table. veafRadio = {} ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Global settings. Stores the script constants ------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Identifier. All output in DCS.log will start with this. veafRadio.Id = "RADIO" --- Version. veafRadio.Version = "1.3.0" -- trace level, specific to this module --veafRadio.LogLevel = "trace" veaf.loggers.new(veafRadio.Id, veafRadio.LogLevel) veafRadio.RadioMenuName = "VEAF" -- constants used to determine how the radio menu is set up veafRadio.USAGE_ForAll = 0 veafRadio.USAGE_ForGroup = 1 veafRadio.USAGE_ForUnit = 2 -- delay for the actual refresh veafRadio.refreshRadioMenu_DELAY = 1 --- Key phrase to look for in the mark text which triggers the command. veafRadio.Keyphrase = "_radio" --- number of seconds between beacons checks veafRadio.BEACONS_SCHEDULE = 5 ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Do not change anything below unless you know what you are doing! ------------------------------------------------------------------------------------------------------------------------------------------------------------- veafRadio.skipHelpMenus = true --- Humans Units (associative array unitName => unit) veafRadio.humanUnits = {} veafRadio.humanGroups = {} --- This structure contains all the radio menus veafRadio.radioMenu = {} veafRadio.radioMenu.title = veafRadio.RadioMenuName veafRadio.radioMenu.dcsRadioMenu = nil veafRadio.radioMenu.subMenus = {} veafRadio.radioMenu.commands = {} --- Counts the size of the radio menu veafRadio.radioMenuSize = {} veafRadio.beacons = {} ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Utility methods ------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Event handler functions. ------------------------------------------------------------------------------------------------------------------------------------------------------------- function veafRadio.onBirthEvent(event) veaf.loggers.get(veafRadio.Id):trace("veafRadio.onBirthEvent(%s)", event) -- find the originator unit local unitName = event and event.initiator and event.initiator.unitName if not unitName then return end veaf.loggers.get(veafRadio.Id):trace("unitName=%s", unitName) if mist.DBs.humansByName[unitName] then -- it's a human unit veaf.loggers.get(veafRadio.Id):trace("veafRadio.humanUnits=%s", veafRadio.humanUnits) veaf.loggers.get(veafRadio.Id):trace("unitName %s is a human unit", unitName) if not veafRadio.humanUnits[unitName] then -- add the unit to the human units list and rebuild the radio menu veaf.loggers.get(veafRadio.Id):trace("Adding human unit %s", unitName) local groupId = event and event.initiator and event.initiator.unitGroupId local callsign = event and event.initiator and event.initiator.unitPilotName if not callsign then callsign = event and event.initiator and event.initiator.unitCallsign end local unitObject = {name=unitName, spawned=true, groupId=groupId, callsign=callsign} veafRadio.humanUnits[unitName] = {} veafRadio.humanUnits[unitName].spawned = true veafRadio.humanUnits[unitName] = unitObject veaf.loggers.get(veafRadio.Id):trace("veafRadio.humanGroups=%s", veafRadio.humanGroups) if not veafRadio.humanGroups[groupId] then veafRadio.humanGroups[groupId] = {} veafRadio.humanGroups[groupId].callsigns = {} veafRadio.humanGroups[groupId].units = {} end table.insert(veafRadio.humanGroups[groupId].callsigns, callsign) veaf.loggers.get(veafRadio.Id):trace("veafRadio.humanGroups=%s", veafRadio.humanGroups) veafRadio.humanGroups[groupId].units[callsign] = unitObject -- sort callsigns for each group for _, groupData in pairs(veafRadio.humanGroups) do table.sort(groupData.callsigns) end -- refresh the radio menu veaf.loggers.get(veafRadio.Id):debug("refreshRadioMenu() following event %s of human unit %s", event.type and event.type.name, unitName) veafRadio.refreshRadioMenu() end end end --- Function executed when a mark has changed. This happens when text is entered or changed. function veafRadio.onEventMarkChange(eventPos, event) if veafRadio.executeCommand(eventPos, event.text, event.coalition) then -- Delete old mark. veaf.loggers.get(veafRadio.Id):trace(string.format("Removing mark # %d.", event.idx)) trigger.action.removeMark(event.idx) end end function veafRadio.executeCommand(eventPos, eventText, eventCoalition, bypassSecurity) veaf.loggers.get(veafRadio.Id):trace(string.format("veafRadio.executeCommand(%s)", eventText)) -- Check if marker has a text and the veafRadio.keyphrase keyphrase. if eventText ~= nil and eventText:lower():find(veafRadio.Keyphrase) then -- Analyse the mark point text and extract the keywords. local options = veafRadio.markTextAnalysis(eventText) if options then veaf.loggers.get(veafRadio.Id):trace(string.format("options.path=%s",veaf.p(options.path))) -- Check options commands if options.transmit and options.message and options.frequencies and options.name then -- transmit a radio message via SRS veafRadio.transmitMessage(options.message, options.frequencies, options.modulations, options.name, eventCoalition, eventPos, options.quiet) return true elseif options.playmp3 and options.path and options.frequencies and options.name then -- play a MP3 file via SRS veafRadio.playToRadio(options.path, options.frequencies, options.modulations, options.name, eventCoalition, eventPos, options.quiet) return true end else -- None of the keywords matched. return false end end return false end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Analyse the mark text and extract keywords. ------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Extract keywords from mark text. function veafRadio.markTextAnalysis(text) veaf.loggers.get(veafRadio.Id):trace(string.format("markTextAnalysis(%s)", text)) -- Option parameters extracted from the mark text. local switch = {} switch.transmit = false switch.playmp3 = false switch.message = nil switch.frequencies = "251" switch.modulations = "AM" switch.name = "SRS" switch.quiet = false switch.path = nil -- Check for correct keywords. if text:lower():find(veafRadio.Keyphrase .. " transmit") then switch.transmit = true elseif text:lower():find(veafRadio.Keyphrase .. " play") then switch.playmp3 = true else return nil end -- keywords are split by "," local keywords = veaf.split(text, ",") for _, keyphrase in pairs(keywords) do -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma. local str = veaf.breakString(veaf.trim(keyphrase), " ") local key = str[1] local val = str[2] if key:lower() == "message" then -- Set message. veaf.loggers.get(veafRadio.Id):trace(string.format("Keyword message = %s", tostring(val))) switch.message = val elseif key:lower() == "path" then -- Set path. veaf.loggers.get(veafRadio.Id):trace(string.format("Keyword path = %s", tostring(val))) switch.path = val elseif key:lower() == "name" then -- Set name. veaf.loggers.get(veafRadio.Id):trace(string.format("Keyword name = %s", tostring(val))) switch.name = val elseif key:lower() == "quiet" then -- Set quiet. veaf.loggers.get(veafRadio.Id):trace("Keyword quiet found") switch.quiet = true elseif key:lower() == "freq" or key:lower() == "freqs" or key:lower() == "frequency" or key:lower() == "frequencies" then -- Set frequencies. veaf.loggers.get(veafRadio.Id):trace(string.format("Keyword frequencies = %s", tostring(val))) switch.frequencies = val elseif key:lower() == "mod" or key:lower() == "mods" or key:lower() == "modulation" or key:lower() == "modulations" then -- Set modulations. veaf.loggers.get(veafRadio.Id):trace(string.format("Keyword modulations = %s", tostring(val))) switch.modulations = val elseif key:lower() == "path" then -- Set path. veaf.loggers.get(veafRadio.Id):trace(string.format("Keyword path = %s", tostring(val))) switch.path = val end end return switch end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Main event handler (used for PLAYER ENTER UNIT events) ------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Radio menu methods ------------------------------------------------------------------------------------------------------------------------------------------------------------- function veafRadio._proxyMethod(parameters) veaf.loggers.get(veafRadio.Id):trace("parameters="..veaf.p(parameters)) local realMethod, realParameters = veaf.safeUnpack(parameters) veaf.loggers.get(veafRadio.Id):trace("realMethod="..veaf.p(realMethod)) veaf.loggers.get(veafRadio.Id):trace("realParameters="..veaf.p(realParameters)) if veafSecurity.isAuthenticated() then realMethod(realParameters) else veaf.loggers.get(veafRadio.Id):error("Your radio has to be authenticated for '+'' commands") trigger.action.outText("Your radio has to be authenticated for '+'' commands", 5) end end --- Refresh the radio menu, based on stored information --- This is called from another method that has first changed the radio menu information by adding or removing elements function veafRadio.refreshRadioMenu(dontDelay) veaf.loggers.get(veafRadio.Id):debug(string.format("veafRadio.refreshRadioMenu()")) -- delay the refresh if possible if not dontDelay then if not veafRadio.refreshRadioMenuDelayedScheduling then veafRadio.refreshRadioMenuDelayedScheduling = mist.scheduleFunction(veafRadio._refreshRadioMenu,{},timer.getTime()+veafRadio.refreshRadioMenu_DELAY) end else veafRadio._refreshRadioMenu() end end --- actually refresh the radio menu, based on stored information function veafRadio._refreshRadioMenu() veaf.loggers.get(veafRadio.Id):debug(string.format("veafRadio._refreshRadioMenu()")) veafRadio.refreshRadioMenuDelayedScheduling = nil -- completely delete the dcs radio menu veaf.loggers.get(veafRadio.Id):trace("completely delete the dcs radio menu") if veafRadio.radioMenu.dcsRadioMenu then missionCommands.removeItem(veafRadio.radioMenu.dcsRadioMenu) else veaf.loggers.get(veafRadio.Id):info("_refreshRadioMenu() first time : no DCS radio menu yet") end -- create all the commands and submenus in the dcs radio menu veaf.loggers.get(veafRadio.Id):trace("create all the commands and submenus in the dcs radio menu") veafRadio.refreshRadioSubmenu(nil, veafRadio.radioMenu) end function veafRadio._addCommand(groupId, title, menu, command, parameters) if not command.method then veaf.loggers.get(veafRadio.Id):error("ERROR - missing method for command " .. title) end local _title = title local _method = command.method local _parameters = parameters if command.isSecured then veaf.loggers.get(veafRadio.Id):trace("adding secured command") _method = veafRadio._proxyMethod _parameters = {command.method, _parameters} if veafSecurity.isAuthenticated() then _title = "-" .. title else _title = "+" .. title end end veaf.loggers.get(veafRadio.Id):trace("_title=%s", veaf.p(_title)) veaf.loggers.get(veafRadio.Id):trace("_parameters=%s", veaf.p(_parameters)) if groupId then veaf.loggers.get(veafRadio.Id):trace("adding for group %s command %s",groupId or "", _title or "") missionCommands.addCommandForGroup(groupId, _title, menu, _method, _parameters) else veaf.loggers.get(veafRadio.Id):trace("adding for all command %s",_title or "") missionCommands.addCommand(_title, menu, _method, _parameters) end end function veafRadio.refreshRadioSubmenu(parentRadioMenu, radioMenu) veaf.loggers.get(veafRadio.Id):debug("veafRadio.refreshRadioSubmenu %s", veaf.p(veaf.ifnn(radioMenu, "title"))) if not radioMenu or not radioMenu.title then return end -- create the radio menu in DCS if parentRadioMenu then radioMenu.dcsRadioMenu = missionCommands.addSubMenu(radioMenu.title, parentRadioMenu.dcsRadioMenu) else radioMenu.dcsRadioMenu = missionCommands.addSubMenu(radioMenu.title) end -- create the commands in the radio menu table.sort(radioMenu.commands, function(a, b) if a.title and b.title then return a.title < b.title else return false end end) for count = 1,#radioMenu.commands do local command = radioMenu.commands[count] veaf.loggers.get(veafRadio.Id):trace(string.format("command=%s",veaf.p(command))) if not command.usage then command.usage = veafRadio.USAGE_ForAll end if command.usage ~= veafRadio.USAGE_ForAll then -- build menu for each player group local alreadyDoneGroups = {} for groupId, groupData in pairs(veafRadio.humanGroups) do veaf.loggers.get(veafRadio.Id):trace(string.format("groupId=%s",veaf.p(groupId))) for _, callsign in pairs(groupData.callsigns) do veaf.loggers.get(veafRadio.Id):trace(string.format("callsign=%s",veaf.p(callsign))) local unitData = groupData.units[callsign] local unitName = unitData.name veaf.loggers.get(veafRadio.Id):trace(string.format("unitName=%s",veaf.p(unitName))) local humanUnit = veafRadio.humanUnits[unitName] veaf.loggers.get(veafRadio.Id):trace(string.format("humanUnit=%s",veaf.p(humanUnit))) if humanUnit and humanUnit.spawned then veaf.loggers.get(veafRadio.Id):debug(string.format("add radio command for player unit %s",veaf.p(unitName))) -- add radio command by player unit or group local parameters = command.parameters if parameters == nil then parameters = unitName else parameters = { command.parameters } table.insert(parameters, unitName) end local _title = command.title if command.usage == veafRadio.USAGE_ForUnit then _title = callsign .. " - " .. command.title end if alreadyDoneGroups[groupId] == nil or command.usage == veafRadio.USAGE_ForUnit then veafRadio._addCommand(groupId, _title, radioMenu.dcsRadioMenu, command, parameters) end alreadyDoneGroups[groupId] = true end end end else veafRadio._addCommand(nil, command.title, radioMenu.dcsRadioMenu, command, command.parameters) end end -- recurse to create the submenus in the radio menu table.sort(radioMenu.subMenus, function(a, b) if a.title and b.title then return a.title < b.title else return false end end) for count = 1,#radioMenu.subMenus do local subMenu = radioMenu.subMenus[count] veafRadio.refreshRadioSubmenu(radioMenu, subMenu) end end function veafRadio.addCommandToMainMenu(title, method) return veafRadio._addCommandToMainMenu(title, method, false) end function veafRadio.addSecuredCommandToMainMenu(title, method) return veafRadio._addCommandToMainMenu(title, method, true) end function veafRadio._addCommandToMainMenu(title, method, isSecured) return veafRadio._addCommandToSubmenu(title, nil, method, nil, nil, isSecured) end function veafRadio.addCommandToSubmenu(title, radioMenu, method, parameters, usage) return veafRadio._addCommandToSubmenu(title, radioMenu, method, parameters, usage, false) end function veafRadio.addSecuredCommandToSubmenu(title, radioMenu, method, parameters, usage) return veafRadio._addCommandToSubmenu(title, radioMenu, method, parameters, usage, true) end function veafRadio._addCommandToSubmenu(title, radioMenu, method, parameters, usage, isSecured) veaf.loggers.get(veafRadio.Id):debug(string.format("_addCommandToSubmenu(%s)",veaf.p(title))) local command = {} command.title = title command.method = method command.parameters = parameters command.isSecured = isSecured command.usage = usage if command.usage == nil then command.usage = veafRadio.USAGE_ForAll end local menu = veafRadio.radioMenu if radioMenu then menu = radioMenu end -- add command to menu table.insert(menu.commands, command) return command end function veafRadio.delCommand(radioMenu, title) for count = 1,#radioMenu.commands do local command = radioMenu.commands[count] if command.title == title then table.remove(radioMenu.commands, count) return true end end return false end function veafRadio.addMenu(title) return veafRadio.addSubMenu(title, nil) end function veafRadio.addSubMenu(title, radioMenu) local subMenu = {} subMenu.title = title subMenu.dcsRadioMenu = nil subMenu.subMenus = {} subMenu.commands = {} local menu = veafRadio.radioMenu if radioMenu then menu = radioMenu end -- add subMenu to menu table.insert(menu.subMenus, subMenu) return subMenu end function veafRadio.clearSubmenu(subMenu) if not subMenu then veaf.loggers.get(veafRadio.Id):error("veafRadio.clearSubmenu() subMenu parameter is nil !") return end veaf.loggers.get(veafRadio.Id):debug(string.format("veafRadio.clearSubmenu(%s)",subMenu.title)) subMenu.subMenus = {} subMenu.commands = {} end function veafRadio.delSubmenu(subMenu, radioMenu) if not subMenu then veaf.loggers.get(veafRadio.Id):error("veafRadio.delSubmenu() subMenu parameter is nil !") return end local menu = veafRadio.radioMenu if radioMenu then menu = radioMenu end veaf.arrayRemoveWhen(menu.subMenus, function(t, i, j) -- Return true to keep the value, or false to discard it. --veaf.loggers.get(veafRadio.Id):trace("searching for " .. subMenu.title) local v = menu.subMenus[i] --veaf.loggers.get(veafRadio.Id):trace("checking " .. v.title) if v == subMenu or v.title == subMenu then --veaf.loggers.get(veafRadio.Id):trace("found ! removing " .. v.title) return false else --veaf.loggers.get(veafRadio.Id):trace("keeping " .. v.title) return true end end); end -- build a paginated submenu (internal paginating method) local function _buildRadioMenuPage(menu, titles, elementsByTitle, addCommandToSubmenuMethod, pageSize, startIndex) veaf.loggers.get(veafRadio.Id):trace(string.format("_buildRadioMenuPage(pageSize=%s, startIndex=%s)",tostring(pageSize), tostring(startIndex))) local titlesCount = #titles veaf.loggers.get(veafRadio.Id):trace(string.format("titlesCount = %d",titlesCount)) local pageSize = pageSize if not pageSize then pageSize = 10 end local endIndex = titlesCount if endIndex - startIndex >= pageSize then endIndex = startIndex + pageSize - 2 end veaf.loggers.get(veafRadio.Id):trace(string.format("endIndex = %d",endIndex)) veaf.loggers.get(veafRadio.Id):trace(string.format("adding commands from %d to %d",startIndex, endIndex)) for index = startIndex, endIndex do local title = titles[index] veaf.loggers.get(veafRadio.Id):trace(string.format("titles[%d] = %s",index, title)) local element = elementsByTitle[title] addCommandToSubmenuMethod(menu, title, element) end if endIndex < titlesCount then veaf.loggers.get(veafRadio.Id):trace("adding next page menu") local nextPageMenu = veafRadio.addSubMenu("Next page", menu) _buildRadioMenuPage(nextPageMenu, titles, elementsByTitle, addCommandToSubmenuMethod, 10, endIndex+1) end end -- build a paginated submenu (main method) function veafRadio.addPaginatedRadioElements(radioMenu, addCommandToSubmenuMethod, elements, titleAttribute, sortAttribute) veaf.loggers.get(veafRadio.Id):trace(string.format("veafRadio.addPaginatedRadioElements() : elements=%s",veaf.p(elements))) if not addCommandToSubmenuMethod then veaf.loggers.get(veafRadio.Id):error("veafRadio.addPaginatedRadioMenu : addCommandToSubmenuMethod is mandatory !") return end local pageSize = 10 - #radioMenu.commands local sortedElements = {} local sortAttribute = sortAttribute or "sort" local titleAttribute = titleAttribute or "title" for name, element in pairs(elements) do local sortValue = element[sortAttribute] if not sortValue then sortValue = name end table.insert(sortedElements, {element=element, sort=sortValue, title=name}) end local compare = function(a,b) if not(a) then a = {} end if not(a["sort"]) then a["sort"] = 0 end if not(b) then b = {} end if not(b["sort"]) then b["sort"] = 0 end return a["sort"] < b["sort"] end table.sort(sortedElements, compare) local sortedTitles = {} local elementsByTitle = {} for i = 1, #sortedElements do local title = sortedElements[i].element[titleAttribute] if not title then title = sortedElements[i].title end table.insert(sortedTitles, title) elementsByTitle[title] = sortedElements[i].element end veaf.loggers.get(veafRadio.Id):trace("sortedTitles="..veaf.p(sortedTitles)) _buildRadioMenuPage(radioMenu, sortedTitles, elementsByTitle, addCommandToSubmenuMethod, pageSize, 1) --veafRadio.refreshRadioMenu() end -- build a paginated submenu (main method) function veafRadio.addPaginatedRadioMenu(title, radioMenu, addCommandToSubmenuMethod, elements, titleAttribute, sortAttribute) veaf.loggers.get(veafRadio.Id):trace(string.format("veafRadio.addPaginatedRadioMenu(title=%s)",title)) local firstPagePath = veafRadio.addSubMenu(title, radioMenu) veafRadio.addPaginatedRadioElements(firstPagePath, addCommandToSubmenuMethod, elements, titleAttribute, sortAttribute) return firstPagePath end function veafRadio.getHumanUnitOrWingman(unitName) local result = Unit.getByName(unitName) if not result then local unitData = veafRadio.humanUnits[unitName] veaf.loggers.get(veafRadio.Id):trace(string.format("unitData=%s",veaf.p(unitData))) if unitData and unitData.groupId then local mistGroup = mist.DBs.groupsById[unitData.groupId] veaf.loggers.get(veafRadio.Id):trace(string.format("mistGroup=%s",veaf.p(mistGroup))) if mistGroup then local group = Group.getByName(mistGroup.groupName) if group then veaf.loggers.get(veafRadio.Id):trace(string.format("group=%s",veaf.p(group))) veaf.loggers.get(veafRadio.Id):trace(string.format("group:getUnits()=%s",veaf.p(group:getUnits()))) for _, groupUnit in pairs(group:getUnits()) do if not result then result = groupUnit end end end end end end if result then veaf.loggers.get(veafRadio.Id):trace(string.format("result=%s",veaf.p(result))) veaf.loggers.get(veafRadio.Id):trace(string.format("result:getName()=%s",veaf.p(result:getName()))) end return result end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- radio beacons ------------------------------------------------------------------------------------------------------------------------------------------------------------- function veafRadio.startBeacon(name, firstRunDelay, secondsBetweenRepeats, frequencies, modulations, message, mp3, coalition) veaf.loggers.get(veafRadio.Id):debug("startBeacon(name=%s, firstRunDelay=%s, secondsBetweenRepeats=%s, coalition=%s, frequencies=%s, modulations=%s, message=%s, mp3=%s)", veaf.p(name), veaf.p(firstRunDelay), veaf.p(secondsBetweenRepeats), veaf.p(coalition), veaf.p(frequencies), veaf.p(modulations), veaf.p(message), veaf.p(mp3)) local beacon = veafRadio.beacons[name:lower()] if not beacon then beacon = {} end beacon.name = name beacon.secondsBetweenRepeats = secondsBetweenRepeats beacon.nextRun = timer.getTime()+firstRunDelay beacon.frequencies = frequencies beacon.modulations = modulations beacon.coalition = coalition beacon.message = message beacon.mp3 = mp3 veaf.loggers.get(veafRadio.Id):debug(string.format("adding beacon %s", tostring(name))) veafRadio.beacons[name:lower()] = beacon end function veafRadio._runBeacons() --veaf.loggers.get(veafRadio.Id):trace("_runBeacons()") local now = timer.getTime() --veaf.loggers.get(veafRadio.Id):debug(string.format("now = %s", tostring(now))) for name, beacon in pairs(veafRadio.beacons) do --veaf.loggers.get(veafRadio.Id):trace(string.format("checking %s supposed to run at %s", tostring(beacon.name), tostring(beacon.nextRun))) if beacon.nextRun <= now then --veaf.loggers.get(veafRadio.Id):trace(string.format("running beacon %s", tostring(name))) if beacon.message then veafRadio.transmitMessage(beacon.message, beacon.frequencies, beacon.modulations, beacon.name, beacon.coalition, nil, true) elseif beacon.mp3 then veafRadio.playToRadio(beacon.mp3, beacon.frequencies, beacon.modulations, beacon.name, beacon.coalition, nil, true) end beacon.nextRun = now + beacon.secondsBetweenRepeats end end --veaf.loggers.get(veafRadio.Id):trace(string.format("rescheduling in %s seconds", tostring(veafRadio.BEACONS_SCHEDULE))) mist.scheduleFunction(veafRadio._runBeacons,{},timer.getTime()+veafRadio.BEACONS_SCHEDULE) end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- radio utilities ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- transmit a radio message or play a mp3 file via SRS function veafRadio._transmitViaSRS(message, file, frequencies, modulations, name, coalition, eventPos) veaf.loggers.get(veafRadio.Id):debug("transmitMessage(name=%s, coalition=%s, frequencies=%s, modulations=%s, message=%s, file=%s)", veaf.p(name), veaf.p(coalition), veaf.p(frequencies), veaf.p(modulations), veaf.p(message), veaf.p(file)) local posOption = "" if eventPos then veaf.loggers.get(veafRadio.Id):trace(string.format("eventPos=%s",veaf.p(eventPos))) local lat, lon, alt = coord.LOtoLL(eventPos) posOption = string.format("-L %d -O %d -A %d", lat, lon, alt) end local contentOption = "" if message then contentOption = string.format("-t \"%s\"", message) elseif file then contentOption = string.format("-i \"%s\"", file) else veaf.loggers.get(veafRadio.Id):error("no message nor file for veafRadio._transmitViaSRS()!") return end local l_os = os if not l_os and SERVER_CONFIG and SERVER_CONFIG.getModule then l_os = SERVER_CONFIG.getModule("os") end if l_os and STTS then local cmd = string.format("start /min \"%s\" \"%s\\%s\" %s -f %s -m %s -c %s -p %s -n \"%s\" %s", STTS.DIRECTORY, STTS.DIRECTORY, STTS.EXECUTABLE, contentOption, frequencies, modulations, coalition, STTS.SRS_PORT, name, posOption) veaf.loggers.get(veafRadio.Id):trace(string.format("executing os command %s", cmd)) local result = l_os.execute(cmd) if result == nil then veaf.loggers.get(veafRadio.Id):warn(string.format("Nil result after executing os command %s", cmd)) end return result end end -- transmit a radio message via SRS function veafRadio.transmitMessage(message, frequencies, modulations, name, coalition, eventPos, quiet) veaf.loggers.get(veafRadio.Id):debug("transmitMessage(name=%s, coalition=%s, frequencies=%s, modulations=%s, message=%s)", veaf.p(name), veaf.p(coalition), veaf.p(frequencies), veaf.p(modulations), veaf.p(message)) if eventPos then veaf.loggers.get(veafRadio.Id):trace(string.format("eventPos=%s",veaf.p(eventPos))) end veafRadio._transmitViaSRS(message, nil, frequencies, modulations, name, coalition, eventPos) if not quiet and coalition then trigger.action.outTextForCoalition(coalition, string.format("%s (%s) : %s", name, frequencies, message), 30) end end -- play a MP3 file via SRS function veafRadio.playToRadio(pathToMP3, frequencies, modulations, name, coalition, eventPos, quiet) veaf.loggers.get(veafRadio.Id):debug("playToRadio(name=%s, coalition=%s, frequencies=%s, modulations=%s, pathToMP3=%s)", veaf.p(name), veaf.p(coalition), veaf.p(frequencies), veaf.p(modulations), veaf.p(pathToMP3)) if eventPos then veaf.loggers.get(veafRadio.Id):trace(string.format("eventPos=%s",veaf.p(eventPos))) end veafRadio._transmitViaSRS(nil, pathToMP3, frequencies, modulations, name, coalition, eventPos) if not quiet and coalition then trigger.action.outTextForCoalition(coalition, string.format("%s (%s) : playing %s", name, frequencies, pathToMP3), 30) end end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- user menus ------------------------------------------------------------------------------------------------------------------------------------------------------------- function veafRadio.createUserMenu(configuration, groupId) veaf.loggers.get(veafRadio.Id):debug("veafRadio.createUserMenu(groupId=%s, configuration=%s)",veaf.p(groupId), veaf.p(configuration)) local function _recursivelyCreateMenu(configuration, parentMenu) veaf.loggers.get(veafRadio.Id):trace("_recursivelyCreateMenu(configuration=%s, parentMenu=%s)",veaf.p(configuration), veaf.p(parentMenu)) local result for _, item in pairs(configuration) do local itemType = item[1] veaf.loggers.get(veafRadio.Id):trace("itemType = [%s]",veaf.p(itemType)) local name = item[2] veaf.loggers.get(veafRadio.Id):trace("name = [%s]",veaf.p(name)) if itemType == "menu" then -- this is a menu with a content local content = item[3] veaf.loggers.get(veafRadio.Id):trace("content = [%s]",veaf.p(content)) veaf.loggers.get(veafRadio.Id):trace("creating menu name=%s",veaf.p(name)) if groupId ~= nil then result = missionCommands.addSubMenuForGroup(groupId, name, parentMenu) else result = missionCommands.addSubMenu(name, parentMenu) end -- recurse if needed if content ~= nil and #content > 0 then _recursivelyCreateMenu(content, result) end else -- this is a command with a function local aFunction = item[3] veaf.loggers.get(veafRadio.Id):trace("aFunction = [%s]",veaf.p(aFunction)) local parameters = item[4] veaf.loggers.get(veafRadio.Id):trace("parameters = [%s]",veaf.p(parameters)) veaf.loggers.get(veafRadio.Id):trace("creating command name=%s",veaf.p(name)) if groupId ~= nil then missionCommands.addCommandForGroup(groupId, name, parentMenu, aFunction, parameters) else missionCommands.addCommand(name, parentMenu, aFunction, parameters) end end end end _recursivelyCreateMenu(configuration, nil) end -- helper functions for user menus local spawnCapFunction = function () end function veafRadio.menu(name, ...) return { "menu", name, {...} } end function veafRadio.command(name, aFunction, parameters) return { "command", name, aFunction, parameters } end function veafRadio.mainmenu(...) return { ... } end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- initialisation ------------------------------------------------------------------------------------------------------------------------------------------------------------- function veafRadio.initialize(skipHelpMenus) -- Find the path of the SRS radio configuration script -- We're going to need it to define : -- STTS.DIRECTORY --- STTS.SRS_PORT local srsConfigPath=nil local l_lfs = lfs if not l_lfs and SERVER_CONFIG and SERVER_CONFIG.getModule then l_lfs = SERVER_CONFIG.getModule("lfs") end if l_lfs then srsConfigPath = l_lfs.writedir() .. "\\DCS-SimpleRadio-Standalone\\SRS_for_scripting_config.lua" veaf.loggers.get(veafRadio.Id):debug(string.format("srsConfigPath = %s", tostring(srsConfigPath))) --local test = l_lfs.currentdir() --veaf.loggers.get(veafRadio.Id):debug(string.format("test = %s", tostring(test))) if srsConfigPath then -- execute the script local file = loadfile(srsConfigPath) if file then file() veaf.loggers.get(veafRadio.Id):info("SRS configuration file loaded") if STTS then STTS.MP3_FOLDER = l_lfs.writedir() .."\\..\\..\\Music" veaf.loggers.get(veafRadio.Id):trace(string.format("STTS.SRS_PORT = %s", tostring(STTS.SRS_PORT))) veaf.loggers.get(veafRadio.Id):trace(string.format("STTS.DIRECTORY = %s", tostring(STTS.DIRECTORY))) veaf.loggers.get(veafRadio.Id):trace(string.format("STTS.EXECUTABLE = %s", tostring(STTS.EXECUTABLE))) end else veaf.loggers.get(veafRadio.Id):warn(string.format("Error while loading SRS configuration file [%s]",srsConfigPath)) end end end veafRadio.skipHelpMenus = skipHelpMenus or false -- Build the initial radio menu veafRadio.refreshRadioMenu(false) --mist.scheduleFunction(veafRadio._refreshRadioMenu,{},timer.getTime()+15) --TODO check if this is still needed (commented out when added the BIRTH event handler) -- add marker change event handler veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafRadio.onEventMarkChange) -- add human birth event handler veafEventHandler.addCallback("veafRadio.eventHandler", {"S_EVENT_BIRTH", "S_EVENT_PLAYER_ENTER_UNIT"}, veafRadio.onBirthEvent) -- start the beacons veafRadio._runBeacons() end veaf.loggers.get(veafRadio.Id):info(string.format("Loading version %s", veafRadio.Version))