diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index c77c593..e333b19 100644 Binary files a/Doc/DML Documentation.pdf and b/Doc/DML Documentation.pdf differ diff --git a/Doc/DML Quick Reference.pdf b/Doc/DML Quick Reference.pdf index 61b60ba..d81b7a6 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index c3b444e..36f2b84 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -1,5 +1,5 @@ cfxZones = {} -cfxZones.version = "3.1.2" +cfxZones.version = "3.1.3" -- cf/x zone management module -- reads dcs zones and makes them accessible and mutable @@ -132,6 +132,11 @@ cfxZones.version = "3.1.2" - 3.1.1 - getRGBAVectorFromZoneProperty now supports #RRGGBBAA and #RRGGBB format - owner for all, default 0 - 3.1.2 - getAllZoneProperties has numbersOnly option +- 3.1.3 - new numberArrayFromString() + - new declutterZone() + - new getZoneVolume() + - offsetZone also updates zone bounds when moving zones + - corrected bug in calculateZoneBounds() --]]-- cfxZones.verbose = false @@ -340,7 +345,8 @@ function cfxZones.calculateZoneBounds(theZone) if (vertex.x < ll.x) then ll.x = vertex.x; ul.x = vertex.x end if (vertex.x > lr.x) then lr.x = vertex.x; ur.x = vertex.x end if (vertex.z < ul.z) then ul.z = vertex.z; ur.z = vertex.z end - if (vertex.z > ll.z) then ll.z = vertex.z; lr.z = vertex.z end + --if (vertex.z > ll.z) then ll.z = vertex.z; lr.z = vertex.z end + if (vertex.z > ur.z) then ur.z = vertex.z; ul.z = vertex.z end local dp = dcsCommon.dist(theZone.point, vertex) if dp > pRad then pRad = dp end -- find largst distance to vertex end @@ -351,6 +357,7 @@ function cfxZones.calculateZoneBounds(theZone) bounds.lr = lr bounds.ul = ul bounds.ur = ur + -- we may need to ascertain why we need ul, ur, ll, lr instead of just ll and ur -- store pRad theZone.pRad = pRad -- not sure we'll ever need that, but at least we have it -- trigger.action.outText("+++Zones: poly zone <" .. theZone.name .. "> has pRad = " .. pRad, 30) -- remember to remove me @@ -804,6 +811,66 @@ function cfxZones.getZonesWithAttributeNamed(attributeName, testZones) return attributZones end +-- +-- zone volume management +-- + +function cfxZones.getZoneVolume(theZone) + if not theZone then return nil end + + if (theZone.isCircle) then + -- create a sphere volume + local p = cfxZones.getPoint(theZone) + p.y = land.getHeight({x = p.x, y = p.z}) + local r = theZone.radius + if r < 10 then r = 10 end + local vol = { + id = world.VolumeType.SPHERE, + params = { + point = p, + radius = r + } + } + return vol + elseif (theZone.isPoly) then + --trigger.action.outText("zne: isPointInside: " .. theZone.name .. " is Polyzone!", 30) + -- build the box volume, using the zone's bounds ll and ur points + local lowerLeft = {} + -- we build x = westerm y = southern, Z = alt + local alt = land.getHeight({x=theZone.bounds.ll.x, y = theZone.bounds.ll.z}) - 10 + lowerLeft.x = theZone.bounds.ll.x + lowerLeft.z = theZone.bounds.ll.z + lowerLeft.y = alt -- we go lower + + local upperRight = {} + alt = land.getHeight({x=theZone.bounds.ur.x, y = theZone.bounds.ur.z}) + 10 + upperRight.x = theZone.bounds.ur.x + upperRight.z = theZone.bounds.ur.z + upperRight.y = alt -- we go higher + + -- construct volume + local vol = { + id = world.VolumeType.BOX, + params = { + min = lowerLeft, + max = upperRight + } + } + return vol + else + trigger.action.outText("zne: unknown zone type for <" .. theZone.name .. ">", 30) + end +end + +function cfxZones.declutterZone(theZone) + if not theZone then return end + local theVol = cfxZones.getZoneVolume(theZone) + if theZone.verbose then + dcsCommon.dumpVar2Str("vol", theVol) + end + world.removeJunk(theVol) +end + -- -- units / groups in zone -- @@ -924,6 +991,18 @@ function cfxZones.offsetZone(theZone, dx, dz) theZone.poly[v].x = theZone.poly[v].x + dx theZone.poly[v].z = theZone.poly[v].z + dz end + + -- update zone bounds + theZone.bounds.ll.x = theZone.bounds.ll.x + dx + theZone.bounds.lr.x = theZone.bounds.lr.x + dx + theZone.bounds.ul.x = theZone.bounds.ul.x + dx + theZone.bounds.ur.x = theZone.bounds.ur.x + dx + + theZone.bounds.ll.z = theZone.bounds.ll.z + dz + theZone.bounds.lr.z = theZone.bounds.lr.z + dz + theZone.bounds.ul.z = theZone.bounds.ul.z + dz + theZone.bounds.ur.z = theZone.bounds.ur.z + dz + end function cfxZones.moveZoneTo(theZone, x, z) @@ -1886,7 +1965,58 @@ function cfxZones.testZoneFlag(theZone, theFlagName, theMethod, latchName) return testResult, currVal end - +function cfxZones.numberArrayFromString(inString, default) + if not default then default = 0 end + if string.len(inString) < 1 then + trigger.action.outText("+++zne: empty numbers", 30) + return {default, } + end + if cfxZones.verbose then + trigger.action.outText("+++zne: processing <" .. inString .. ">", 30) + end + + local flags = {} + local rawElements = dcsCommon.splitString(inString, ",") + -- go over all elements + for idx, anElement in pairs(rawElements) do + anElement = dcsCommon.trim(anElement) + if dcsCommon.stringStartsWithDigit(anElement) and dcsCommon.containsString(anElement, "-") then + -- interpret this as a range + local theRange = dcsCommon.splitString(anElement, "-") + local lowerBound = theRange[1] + lowerBound = tonumber(lowerBound) + local upperBound = theRange[2] + upperBound = tonumber(upperBound) + if lowerBound and upperBound then + -- swap if wrong order + if lowerBound > upperBound then + local temp = upperBound + upperBound = lowerBound + lowerBound = temp + end + -- now add add numbers to flags + for f=lowerBound, upperBound do + table.insert(flags, tostring(f)) + end + else + -- bounds illegal + trigger.action.outText("+++zne: ignored range <" .. anElement .. "> (range)", 30) + end + else + -- single number + f = dcsCommon.trim(anElement) + f = tonumber(f) + if f then + table.insert(flags, f) + end + end + end + if #flags < 1 then flags = {default, } end + if cfxZones.verbose then + trigger.action.outText("+++zne: <" .. #flags .. "> flags total", 30) + end + return flags +end function cfxZones.flagArrayFromString(inString) -- original code from RND flag diff --git a/modules/cloneZone.lua b/modules/cloneZone.lua index 5a85717..01b8f99 100644 --- a/modules/cloneZone.lua +++ b/modules/cloneZone.lua @@ -1,5 +1,5 @@ cloneZones = {} -cloneZones.version = "1.7.1" +cloneZones.version = "1.7.3" cloneZones.verbose = false cloneZones.requiredLibs = { "dcsCommon", -- always @@ -94,6 +94,8 @@ cloneZones.respawnOnGroupID = true 1.7.1 - useDelicates handOff for delicates - forcedRespawn passes zone instead of verbose 1.7.2 - onPerimeter attribute + 1.7.3 - declutter option + --]]-- @@ -256,6 +258,9 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" end end + -- declutter + theZone.declutter = cfxZones.getBoolFromZoneProperty(theZone, "declutter", false) + -- watchflags theZone.cloneTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") @@ -1543,6 +1548,14 @@ function cloneZones.spawnWithCloner(theZone) cloneZones.invokeCallbacks(theZone, "wiped", {}) end + -- declutter? + if theZone.declutter then + cfxZones.declutterZone(theZone) + if theZone.verbose then + trigger.action.outText("+++clnZ: cloner <" .. theZone.name .. "> declutter complete.", 30) + end + end + local theClones, theStatics = cloneZones.spawnWithTemplateForZone(templateZone, theZone) -- reset hasClones so we know our spawns are full and we can -- detect complete destruction diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index f4c1b7a..0023f15 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.8.9" +dcsCommon.version = "2.8.10" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -156,6 +156,13 @@ dcsCommon.version = "2.8.9" 2.8.9 - vAdd supports xy and xyz - vSub supports xy and xyz - vMultScalar supports xy and xyz +2.8.10 - tacan2freq now integrated with module (blush) + - array2string cosmetic default + - vMultScalar corrected bug in accessing b.z + - new randomLetter() + - new getPlayerUnit() + - new getMapName() + - new getMagDeclForPoint() --]]-- @@ -2236,13 +2243,14 @@ end end function dcsCommon.array2string(inArray, deli) - if not deli then deli = "," end + if not deli then deli = ", " end if type(inArray) ~= "table" then return "" end local s = "" local count = 0 for idx, ele in pairs(inArray) do if count > 0 then s = s .. deli .. " " end s = s .. ele + count = count + 1 end return s end @@ -2475,6 +2483,7 @@ end end function dcsCommon.dumpVar2Str(key, value, prefix, inrecursion) + -- dumps to screen, not string if not inrecursion then -- output a marker to find in the log / screen trigger.action.outText("*** dcsCommon vardump START",30) @@ -2599,7 +2608,7 @@ end end -- based on buzzer1977's idea, channel is number, eg in 74X, channel is 74, mode is "X" - function tacan2freq(channel, mode) + function dcsCommon.tacan2freq(channel, mode) if not mode then mode = "X" end if not channel then channel = 1 end if type(mode) ~= "string" then mode = "X" end @@ -2712,7 +2721,7 @@ function dcsCommon.vMultScalar(a, f) if not f then f = 0 end r.x = a.x * f r.y = a.y * f - if a.z and b.z then + if a.z then r.z = a.z * f end return r @@ -2812,15 +2821,6 @@ function dcsCommon.isTroopCarrier(theUnit, carriers) return dcsCommon.isTroopCarrierType(uType, carriers) end -function dcsCommon.isPlayerUnit(theUnit) - -- new patch. simply check if getPlayerName returns something - if not theUnit then return false end - if not Unit.isExist(theUnit) then return end - if not theUnit.getPlayerName then return false end -- map/static object - local pName = theUnit:getPlayerName() - if pName then return true end - return false -end function dcsCommon.getAllExistingPlayerUnitsRaw() local apu = {} @@ -3360,6 +3360,14 @@ function dcsCommon.spellString(inString) return res end +dcsCommon.letters = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", +"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", } +function dcsCommon.randomLetter(lowercase) + local theLetter = dcsCommon.pickRandom(dcsCommon.letters) + if lowercase then theLetter = string.lower(theLetter) end + return theLetter +end + -- -- RGBA from hex -- @@ -3401,6 +3409,55 @@ function dcsCommon.playerName2Coalition(playerName) return 0 end +function dcsCommon.isPlayerUnit(theUnit) + -- new patch. simply check if getPlayerName returns something + if not theUnit then return false end + if not Unit.isExist(theUnit) then return end + if not theUnit.getPlayerName then return false end -- map/static object + local pName = theUnit:getPlayerName() + if pName then return true end + return false +end + +function dcsCommon.getPlayerUnit(name) + for coa = 1, 2 do + local players = coalition.getPlayers(coa) + for idx, theUnit in pairs(players) do + if theUnit:getPlayerName() == name then return theUnit end + end + end + return nil +end + +-- +-- theater and theater-related stuff +-- +function dcsCommon.getMapName() + return env.mission.theatre +end + +dcsCommon.magDecls = {Caucasus = 6.5, + MarianaIslands = 1, + Nevada = 12, + PersianGulf = 2, + Syria = 4, + Normandy = -12 -- 1944, -1 in 2016 + -- SinaiMap still missing + -- Falklands still missing, big differences + } + +function dcsCommon.getMagDeclForPoint(point) + -- WARNING! Approximations only, map-wide, not adjusted for year nor location! + -- serves as a stub for the day when DCS provides correct info + local map = dcsCommon.getMapName() + local decl = dcsCommon.magDecls[map] + if not decl then + trigger.action.outText("+++dcsC: unknown map <" .. map .. ">, using dclenation 0", 30) + decl = 0 + end + return decl +end + -- -- iterators -- diff --git a/modules/tacan.lua b/modules/tacan.lua new file mode 100644 index 0000000..b994266 --- /dev/null +++ b/modules/tacan.lua @@ -0,0 +1,430 @@ +tacan = {} +tacan.version = "1.0.0" +--[[-- +Version History + 1.0.0 - initial version + +--]]-- +tacan.verbose = false +tacan.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course +} +tacan.tacanZones = {} + + +function tacan.createTacanZone(theZone) + theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", true) + local channels = cfxZones.getStringFromZoneProperty(theZone, "channel", "1") + theZone.channels = cfxZones.numberArrayFromString(channels, 1) + if theZone.verbose or tacan.verbose then + trigger.action.outText("+++tcn: new tacan <" .. theZone.name .. "> for channels [" .. dcsCommon.array2string(theZone.channels, ", ") .. "]", 30) + end + + local mode = cfxZones.getStringFromZoneProperty(theZone, "mode", "X") + mode = string.upper(mode) + theZone.modes = cfxZones.flagArrayFromString(mode) + if theZone.verbose or tacan.verbose then + trigger.action.outText("+++tcn: modes [" .. dcsCommon.array2string(theZone.modes, ", ") .. "]", 30) + end + + theZone.coa = cfxZones.getCoalitionFromZoneProperty(theZone, "tacan", 0) + + theZone.heading = cfxZones.getNumberFromZoneProperty(theZone, "heading", 0) + theZone.heading = theZone.heading * 0.0174533 -- convert to rads + local callsign = cfxZones.getStringFromZoneProperty(theZone, "callsign", "TXN") + callsign = string.upper(callsign) + theZone.callsigns = cfxZones.flagArrayFromString(callsign) + if theZone.verbose or tacan.verbose then + trigger.action.outText("+++tcn: callsigns [" .. dcsCommon.array2string(theZone.callsigns) .. "]", 30) + end + + theZone.rndLoc = cfxZones.getBoolFromZoneProperty(theZone, "rndLoc", false) +-- theZone.method = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") + theZone.triggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") + if cfxZones.hasProperty(theZone, "deploy?") then + theZone.deployFlag = cfxZones.getStringFromZoneProperty(theZone, "deploy?", "") + theZone.lastDeployFlagValue = cfxZones.getFlagValue(theZone.deployFlag, theZone) + end + if (not theZone.deployFlag) and (not theZone.onStart) then + trigger.action.outText("+++tacan: WARNING: tacan zone <> is late activation and has no activation flag, will never activate.", 30) + end + + theZone.spawnedTACANS = {} -- for GC and List + theZone.preWipe = cfxZones.getBoolFromZoneProperty(theZone, "preWipe", true) + + if cfxZones.hasProperty(theZone, "destroy?") then + theZone.destroyFlag = cfxZones.getStringFromZoneProperty(theZone, "destroy?", "") + theZone.lastDestroyFlagValue = cfxZones.getFlagValue(theZone.destroyFlag, theZone) + end + + if cfxZones.hasProperty(theZone, "c#") then + theZone.channelOut = cfxZones.getStringFromZoneProperty(theZone, "C#", "") + end + + theZone.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", false) + + -- interface to groupTracker + if cfxZones.hasProperty(theZone, "trackWith:") then + theZone.trackWith = cfxZones.getStringFromZoneProperty(theZone, "trackWith:", "") + end + + -- see if we need to deploy now + if theZone.onStart then + tacan.TacanFromZone(theZone, true) -- true = silent + end +end + +-- hand off to tracker +-- from cloneZones +function tacan.handoffTracking(theGroup, theZone) + if not groupTracker then + trigger.action.outText("+++tacan: <" .. theZone.name .. "> attribute 'trackWith:' requires groupTracker module", 30) + return + end + local trackerName = theZone.trackWith + -- now assemble a list of all trackers + if tacan.verbose or theZone.verbose then + trigger.action.outText("+++tacan: tacan tracked with: " .. trackerName, 30) + end + + local trackerNames = {} + if dcsCommon.containsString(trackerName, ',') then + trackerNames = dcsCommon.splitString(trackerName, ',') + else + table.insert(trackerNames, trackerName) + end + for idx, aTrk in pairs(trackerNames) do + local theName = dcsCommon.trim(aTrk) + if theName == "*" then theName = theZone.name end + local theTracker = groupTracker.getTrackerByName(theName) + if not theTracker then + trigger.action.outText("+++tacan: <" .. theZone.name .. ">: cannot find tracker named <".. theName .. ">", 30) + else + groupTracker.addGroupToTracker(theGroup, theTracker) + if tacan.verbose or theZone.verbose then + trigger.action.outText("+++tacan: added " .. theGroup:getName() .. " to tracker " .. theName, 30) + end + end + end +end + +-- create a tacan +function tacan.createTacanInZone(theZone, channel, mode, callsign) + local point = cfxZones.getPoint(theZone) + local name = theZone.name + local heading = theZone.heading + local unitID = dcsCommon.numberUUID() + if theZone.rndLoc then + point = cfxZones.createRandomPointInZone(theZone) + end + local data = tacan.buildTacanData(name, channel, mode, callsign, point, unitID, heading) + if theZone.verbose or tacan.verbose then + trigger.action.outText("+++tcn: new TACAN for <" .. theZone.name .. ">: ch<" .. channel .. ">, mode <" .. mode .. ">, call <" .. callsign .. ">", 30) + end + data.thePoint = point -- save location + --local s = dcsCommon.dumpVar2Str("data", data) + local coa = theZone.coa -- neutral + local cty = dcsCommon.getACountryForCoalition(coa) + local theCopy = dcsCommon.clone(data) + local theGroup = coalition.addGroup(cty, Group.Category.GROUND, data) + + -- handoff for tracking + if theZone.trackWith then + tacan.handoffTracking(theGroup, theZone) + end + + -- add to my spawns for GC to watch over + local t = {} + t.activeMode = mode + t.activeCallsign = callsign + t.activeChan = channel + t.theGroup = theGroup + t.theData = theCopy + table.insert(theZone.spawnedTACANS, t) + + -- run a GC cycle + tacan.GC(true) + return theGroup, theCopy +end + +function tacan.TacanFromZone(theZone, silent) + local channel = tonumber(dcsCommon.pickRandom(theZone.channels)) + local mode = dcsCommon.pickRandom(theZone.modes) + local callsign = dcsCommon.pickRandom(theZone.callsigns) + + if theZone.preWipe and theZone.activeTacan then + Group.destroy(theZone.activeTacan) + theZone.activeTacan = nil + end + + local theGroup, data = tacan.createTacanInZone(theZone, channel, mode, callsign) + theZone.activeTacan = theGroup + theZone.activeChan = channel + if theZone.channelOut then + trigger.action.setUserFlag(theZone.channelOut, channel) + end + + theZone.activeMode = mode + theZone.activeCallsign = callsign + theZone.activeName = data.name + if theGroup then + if theZone.verbose or tacan.verbose then + trigger.action.outText("+++tcn: created tacan <" .. data.name ..">", 30) + end + end + + if (not silent) and theZone.announcer then + local str = "NOTAM: Deployed new TACAN " .. theZone.name .. " <" .. callsign .. ">, channel " .. channel .. mode .. ", active now" + if theZone.coa == 0 then + trigger.action.outText(str, 30) + else + trigger.action.outTextForCoalition(theZone.coa, str, 30) + end + end +end + +function tacan.destroyTacan(theZone, announce) + if theZone.activeTacan then + Group.destroy(theZone.activeTacan) -- only destroys last allocated + theZone.activeTacan = nil + + if announce then + local coa = theZone.coa + local str = "NOTAM: TACAN " .. theZone.name .. " <" .. theZone.activeCallsign .. "> deactivated" + if coa == 0 then + trigger.action.outText(str, 30) + else + trigger.action.outTextForCoalition(coa, str, 30) + end + end + + end + if theZone.channelOut then + trigger.action.setUserFlag(theZone.channelOut, 0) + end +end + +-- create a TACAN group for the requested TACAN +function tacan.buildTacanData(name, channel, mode, callsign, point, unitID, heading) -- point = (xyz)! + if not heading then heading = 0 end + if not mode then mode = "X" end + mode = string.upper(mode) + local x = point.x + local y = point.z + local alt = land.getHeight({x = x, y = y}) + local g = {} -- group + g.name = name .. dcsCommon.numberUUID() + g.x = x + g.y = y + g.tasks = {} + g.task = "Ground Nothing" + local r = {} -- group.route + g.route = r + local p = {} -- group.route.points + r.points = p -- + local p1 = {} -- group.route.points[1] + p[1] = p1 + p1.alt = alt + 3 + p1.x = x + p1.y = y + local t = {} -- group.route.points[1].task + p1.task = t + t.id = "ComboTask" + local params = {} -- group.route.points[1].task.params + t.params = params + local tasks = {} -- group.route.points[1].task.params.tasks + params.tasks = tasks + local t1 = {} -- group.route.points[1].task.params.tasks[1] + tasks[1] = t1 + + t1.enabled = true + t1.auto = false + t1.id = "WrappedAction" + t1.number = 1 + local pm = {} -- group.route.points[1].task.params.tasks[1].params + t1.params = pm + local a = {} -- group.route.points[1].task.params.tasks[1].params.action + pm.action = a + a.id = "ActivateBeacon" + local ps = {} -- group.route.points[1].task.params.tasks[1].params.action.params + a.params = ps + ps.type = 4 + ps.AA = false + ps.unitID = unitID + ps.modeChannel = mode + ps.channel = channel + ps.system = 18 -- mysterious + ps.callsign = callsign + ps.bearing = true + ps.frequency = dcsCommon.tacan2freq(channel, mode) + if tacan.verbose then + trigger.action.outText("tacan channel <" .. channel .. "> = freq <" .. ps.frequency .. ">", 30) + end + + -- now build unit + local u = {} + g.units = u + local u1 = {} + u[1] = u1 + u1.skill = "High" + u1.type = "TACAN_beacon" + u1.x = x + u1.y = y + u1.name = "u_" .. g.name + u1.unitId = unitID + + return g -- return data block +end + +-- +-- Update +-- +function tacan.update() + timer.scheduleFunction(tacan.update, {}, timer.getTime() + 1) + + for tName, theZone in pairs(tacan.tacanZones) do + -- was start called? + if cfxZones.testZoneFlag(theZone, theZone.deployFlag, theZone.triggerMethod, "lastDeployFlagValue") then + -- we want to deploy and start the tacan. + -- first test if one is still up and running + if theZone.activeTacan and theZone.preWipe then + tacan.destroyTacan(theZone, false) + end + tacan.TacanFromZone(theZone) + end + + if cfxZones.testZoneFlag(theZone, theZone.destroyFlag, theZone.triggerMethod, "lastDestroyFlagValue") then + tacan.destroyTacan(theZone, theZone.announcer) + end + end + +end + + +function tacan.GC(singleCall) + if singleCall then + if tacan.verbose then + trigger.action.outText("+++tacan: single-pass GC invoked", 30) + end + else + timer.scheduleFunction(tacan.update, nil, timer.getTime() + 60) + end + for tName, theZone in pairs(tacan.tacanZones) do + local filteredTACANS = {} + for idx, theActive in pairs(theZone.spawnedTACANS) do + -- check if this tacan still exists + local name = theActive.theData.name -- group name + local theGroup = Group.getByName(name) + if theGroup and Group.isExist(theGroup) then + table.insert(filteredTACANS, theActive) + else + if tacan.verbose then + trigger.action.outText("+++tacan: filtered <" .. name .. ">: no longer exist", 30) + end + end + end + theZone.spawnedTACANS = filteredTACANS + end +end + +-- +-- comms: List TACAN radio command +-- +function tacan.installComms() + tacan.redC = missionCommands.addCommandForCoalition(1, "Available TACAN stations", nil, tacan.listTacan, 1) + tacan.blueC = missionCommands.addCommandForCoalition(2, "Available TACAN stations", nil, tacan.listTacan, 2) + +end + +function tacan.listTacan(side) + timer.scheduleFunction(tacan.doListTacan, side, timer.getTime() + 0.1) +end + +function tacan.doListTacan(args) + tacan.GC(true) -- force GC, once. + + -- collect all neutral and same (as in args)-side tacans + local theTs = {} + for name, theZone in pairs(tacan.tacanZones) do + if theZone.coa == 0 or theZone.coa == args then + for idx, aTacan in pairs(theZone.spawnedTACANS) do + table.insert(theTs, aTacan) + end + end + end + + if #theTs < 1 then + trigger.action.outTextForCoalition(args, "No active TACAN.", 30) + return + end + + local msg = "\nActive TACAN:" + + for idx, aTacan in pairs(theTs) do + msg = msg .. "\n - " .. aTacan.activeCallsign .. ": " .. aTacan.activeChan .. aTacan.activeMode + end + msg = msg .. "\n" + trigger.action.outTextForCoalition(args, msg, 30) +end + +-- +-- Start up: config etc +-- + +function tacan.readConfigZone() + local theZone = cfxZones.getZoneByName("tacanConfig") + if not theZone then theZone = cfxZones.createSimpleZone("tacanConfig") end + + tacan.verbose = theZone.verbose + + tacan.list = cfxZones.getBoolFromZoneProperty(theZone, "list", false) + if cfxZones.hasProperty(theZone, "GUI") then + tacan.list = cfxZones.getBoolFromZoneProperty(theZone, "GUI", false) + end + + if tacan.verbose then + trigger.action.outText("+++tcn: read config", 30) + end +end + +function tacan.start() + -- lib check + if not dcsCommon.libCheck then + trigger.action.outText("cfx Tacan requires dcsCommon", 30) + return false + end + if not dcsCommon.libCheck("cfx TACAN", + tacan.requiredLibs) then + return false + end + + -- read config + tacan.readConfigZone() + + -- set comms + if tacan.list then tacan.installComms() end + + -- collect tacan zones + local tZones = cfxZones.zonesWithProperty("tacan") + for k, aZone in pairs(tZones) do + tacan.createTacanZone(aZone) + tacan.tacanZones[aZone.name] = aZone + end + + -- start update + tacan.update() + + -- start GC + tacan.GC() + + -- say Hi! + trigger.action.outText("cfx Tacan v" .. tacan.version .. " started.",30) +end + +tacan.start() + +--[[-- + Ideas + - moving tacan, as in ndb +--]]-- \ No newline at end of file diff --git a/tutorial & demo missions/demo - Take on TACAN.miz b/tutorial & demo missions/demo - Take on TACAN.miz new file mode 100644 index 0000000..8d4a6c6 Binary files /dev/null and b/tutorial & demo missions/demo - Take on TACAN.miz differ