mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 1.1.4
sequencer messenger radioMenu
This commit is contained in:
parent
4831f7597f
commit
58d81e162f
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
rndFlags = {}
|
||||
rndFlags.version = "1.4.0"
|
||||
rndFlags.version = "1.4.1"
|
||||
rndFlags.verbose = false
|
||||
rndFlags.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -32,6 +32,7 @@ rndFlags.requiredLibs = {
|
||||
1.3.2 - moved flagArrayFromString to dcsCommon
|
||||
- minor clean-up
|
||||
1.4.0 - persistence
|
||||
1.4.1 - a little less verbosity
|
||||
|
||||
--]]
|
||||
|
||||
@ -336,8 +337,10 @@ function rndFlags.start()
|
||||
|
||||
-- process RND Zones
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("RND!")
|
||||
a = dcsCommon.getSizeOfTable(attrZones)
|
||||
trigger.action.outText("RND! zones: " .. a, 30)
|
||||
if rndFlags.verbose then
|
||||
local a = dcsCommon.getSizeOfTable(attrZones)
|
||||
trigger.action.outText("RND! zones: " .. a, 30)
|
||||
end
|
||||
|
||||
-- now create an rnd gen for each one and add them
|
||||
-- to our watchlist
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxMX = {}
|
||||
cfxMX.version = "1.2.2"
|
||||
cfxMX.version = "1.2.3"
|
||||
cfxMX.verbose = false
|
||||
--[[--
|
||||
Mission data decoder. Access to ME-built mission structures
|
||||
@ -21,10 +21,15 @@ cfxMX.verbose = false
|
||||
1.2.2 - fixed ctry bug in countryByName
|
||||
- playerGroupByName
|
||||
- playerUnitByName
|
||||
1.2.3 - groupTypeByName
|
||||
- groupCoalitionByName
|
||||
|
||||
--]]--
|
||||
cfxMX.groupNamesByID = {}
|
||||
cfxMX.groupIDbyName = {}
|
||||
cfxMX.groupDataByName = {}
|
||||
cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"...
|
||||
cfxMX.groupCoalitionByName = {}
|
||||
cfxMX.countryByName ={}
|
||||
cfxMX.linkByName = {}
|
||||
cfxMX.allFixedByName = {}
|
||||
@ -205,11 +210,12 @@ function cfxMX.createCrossReferences()
|
||||
linkUnit = group_data.route.points[1].linkUnit
|
||||
cfxMX.linkByName[aName] = linkUnit
|
||||
end
|
||||
|
||||
cfxMX.groupTypeByName[aName] = category
|
||||
cfxMX.groupNamesByID[aID] = aName
|
||||
cfxMX.groupIDbyName[aName] = aID
|
||||
cfxMX.groupDataByName[aName] = group_data
|
||||
cfxMX.countryByName[aName] = countryID -- !!! was cntry_id
|
||||
cfxMX.groupCoalitionByName[aName] = coaNum
|
||||
|
||||
-- now make the type-specific xrefs
|
||||
if obj_type_name == "helicopter" then
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxReconMode = {}
|
||||
cfxReconMode.version = "2.1.1"
|
||||
cfxReconMode.version = "2.1.2"
|
||||
cfxReconMode.verbose = false -- set to true for debug info
|
||||
cfxReconMode.reconSound = "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" -- to be played when somethiong discovered
|
||||
|
||||
@ -79,6 +79,9 @@ VERSION HISTORY
|
||||
- activate / deactivate by flags
|
||||
2.1.1 - Lat Lon and MGRS also give Elevation
|
||||
- cfxReconMode.reportTime
|
||||
2.1.2 - imperialUnits for elevation
|
||||
- <ele> wildcard in message format
|
||||
- fix for mgrs bug in message (zone coords, not unit)
|
||||
|
||||
cfxReconMode is a script that allows units to perform reconnaissance
|
||||
missions and, after detecting units, marks them on the map with
|
||||
@ -424,13 +427,21 @@ function cfxReconMode.getLocation(theGroup)
|
||||
local theUnit = theGroup:getUnit(1)
|
||||
local currPoint = theUnit:getPoint()
|
||||
local ele = math.floor(land.getHeight({x = currPoint.x, y = currPoint.z}))
|
||||
local units = "m"
|
||||
if cfxReconMode.imperialUnits then
|
||||
ele = math.floor(ele * 3.28084) -- feet
|
||||
units = "ft"
|
||||
else
|
||||
ele = math.floor(ele) -- meters
|
||||
end
|
||||
|
||||
if cfxReconMode.mgrs then
|
||||
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
|
||||
msg = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing .. " Ele " .. ele .."m"
|
||||
msg = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing .. " Ele " .. ele .. units
|
||||
else
|
||||
local lat, lon, alt = coord.LOtoLL(currPoint)
|
||||
lat, lon = dcsCommon.latLon2Text(lat, lon)
|
||||
msg = "Lat " .. lat .. " Lon " .. lon .. " Ele " .. ele .."m"
|
||||
msg = "Lat " .. lat .. " Lon " .. lon .. " Ele " .. ele ..units
|
||||
end
|
||||
return msg
|
||||
end
|
||||
@ -483,12 +494,21 @@ function cfxReconMode.processZoneMessage(inMsg, theZone, theGroup)
|
||||
local theUnit = dcsCommon.getFirstLivingUnit(theGroup)
|
||||
currPoint = theUnit:getPoint()
|
||||
end
|
||||
local ele = math.floor(land.getHeight({x = currPoint.x, y = currPoint.z}))
|
||||
local units = "m"
|
||||
if cfxReconMode.imperialUnits then
|
||||
ele = math.floor(ele * 3.28084) -- feet
|
||||
units = "ft"
|
||||
else
|
||||
ele = math.floor(ele) -- meters
|
||||
end
|
||||
|
||||
local lat, lon, alt = coord.LOtoLL(currPoint)
|
||||
lat, lon = dcsCommon.latLon2Text(lat, lon)
|
||||
outMsg = outMsg:gsub("<lat>", lat)
|
||||
outMsg = outMsg:gsub("<lon>", lon)
|
||||
currPoint = cfxZones.getPoint(theZone)
|
||||
outMsg = outMsg:gsub("<ele>", ele..units)
|
||||
--currPoint = cfxZones.getPoint(theZone)
|
||||
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
|
||||
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
|
||||
outMsg = outMsg:gsub("<mgrs>", mgrs)
|
||||
@ -1019,6 +1039,10 @@ function cfxReconMode.readConfigZone()
|
||||
cfxReconMode.lastDeActivate = cfxZones.getFlagValue(cfxReconMode.deactivate, theZone)
|
||||
end
|
||||
|
||||
cfxReconMode.imperialUnits = cfxZones.getBoolFromZoneProperty(theZone, "imperial", false)
|
||||
if cfxZones.hasProperty(theZone, "imperialUnits") then
|
||||
cfxReconMode.imperialUnits = cfxZones.getBoolFromZoneProperty(theZone, "imperialUnits", false)
|
||||
end
|
||||
|
||||
cfxReconMode.theZone = theZone -- save this zone
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cloneZones = {}
|
||||
cloneZones.version = "1.5.2"
|
||||
cloneZones.version = "1.5.4"
|
||||
cloneZones.verbose = false
|
||||
cloneZones.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -59,6 +59,8 @@ cloneZones.allCObjects = {} -- all clones objects
|
||||
1.5.0 - persistence
|
||||
1.5.1 - fixed static data cloning bug (load & save)
|
||||
1.5.2 - fixed bug in trackWith: referencing wrong cloner
|
||||
1.5.3 - centerOnly/wholeGroups attribute for rndLoc, rndHeading and onRoad
|
||||
1.5.4 - parking for aircraft processing when cloning from template
|
||||
|
||||
|
||||
|
||||
@ -148,7 +150,7 @@ function cloneZones.allGroupsInZoneByData(theZone)
|
||||
end
|
||||
|
||||
function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
||||
if cloneZones.verbose then
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: new cloner " .. theZone.name, 30)
|
||||
end
|
||||
|
||||
@ -292,6 +294,11 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
|
||||
if cfxZones.hasProperty(theZone, "rndLoc") then
|
||||
theZone.rndLoc = cfxZones.getBoolFromZoneProperty(theZone, "rndLoc", false)
|
||||
end
|
||||
theZone.centerOnly = cfxZones.getBoolFromZoneProperty(theZone, "centerOnly", false)
|
||||
if cfxZones.hasProperty(theZone, "wholeGroups") then
|
||||
theZone.centerOnly = cfxZones.getBoolFromZoneProperty(theZone, "wholeGroups", false)
|
||||
end
|
||||
|
||||
theZone.rndHeading = cfxZones.getBoolFromZoneProperty(theZone, "rndHeading", false)
|
||||
|
||||
theZone.onRoad = cfxZones.getBoolFromZoneProperty(theZone, "onRoad", false)
|
||||
@ -308,13 +315,16 @@ end
|
||||
--
|
||||
|
||||
function cloneZones.despawnAll(theZone)
|
||||
if cloneZones.verbose then
|
||||
trigger.action.outText("wiping <" .. theZone.name .. ">", 30)
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: despawn all - wiping zone <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
for idx, aGroup in pairs(theZone.mySpawns) do
|
||||
--trigger.action.outText("++clnZ: despawn all " .. aGroup.name, 30)
|
||||
|
||||
if aGroup:isExist() then
|
||||
if theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: will destroy <" .. aGroup:getName() .. ">", 30)
|
||||
end
|
||||
cloneZones.invokeCallbacks(theZone, "will despawn group", aGroup)
|
||||
Group.destroy(aGroup)
|
||||
end
|
||||
@ -323,7 +333,7 @@ function cloneZones.despawnAll(theZone)
|
||||
-- warning! may be mismatch because we are looking at groups
|
||||
-- not objects. let's see
|
||||
if aStatic:isExist() then
|
||||
if cloneZones.verbose then
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("Destroying static <" .. aStatic:getName() .. ">", 30)
|
||||
end
|
||||
cloneZones.invokeCallbacks(theZone, "will despawn static", aStatic)
|
||||
@ -334,12 +344,47 @@ function cloneZones.despawnAll(theZone)
|
||||
theZone.myStatics = {}
|
||||
end
|
||||
|
||||
function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWaypoints)
|
||||
function cloneZones.assignClosestParking(theData)
|
||||
-- on enter: theData has units with updated x, y
|
||||
-- and waypoint 1 action is From Parking
|
||||
-- and it has at least one unit
|
||||
|
||||
-- let's get the airbase
|
||||
local theRoute = theData.route -- we know it exists
|
||||
local thePoints = theRoute.points
|
||||
local firstPoint = thePoints[1]
|
||||
local loc = {}
|
||||
loc.x = firstPoint.x
|
||||
loc.y = 0
|
||||
loc.z = firstPoint.y
|
||||
local theAirbase = dcsCommon.getClosestAirbaseTo(loc)
|
||||
-- now let's assign free slots closest to unit
|
||||
local slotsTaken = {}
|
||||
local units = theData.units
|
||||
local cat = cfxMX.groupTypeByName[theData.name]
|
||||
for idx, theUnit in pairs(units) do
|
||||
local newSlot = dcsCommon.getClosestFreeSlotForCatInAirbaseTo(cat, theUnit.x, theUnit.y, theAirbase, slotsTaken)
|
||||
if newSlot then
|
||||
local slotNo = newSlot.Term_Index
|
||||
--trigger.action.outText("unit <" .. theUnit.name .. "> old slot <" .. theUnit.parking .. "> to new slot <" .. slotNo .. ">", 30)
|
||||
theUnit.parking_id = nil -- !! or you b screwed
|
||||
theUnit.parking = slotNo -- !! screw parking_ID, they don't match
|
||||
theUnit.x = newSlot.vTerminalPos.x
|
||||
theUnit.y = newSlot.vTerminalPos.z -- !!!
|
||||
table.insert(slotsTaken, slotNo)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWaypoints)
|
||||
-- enter with theData being group's data block
|
||||
-- remember that zoneDelta's [z] modifies theData's y!!
|
||||
theData.x = theData.x + zoneDelta.x
|
||||
theData.y = theData.y + zoneDelta.z -- !!!
|
||||
local units = theData.units
|
||||
local departFromAerodrome = false
|
||||
--local departingAerodrome
|
||||
local fromParking = false
|
||||
for idx, aUnit in pairs(units) do
|
||||
aUnit.x = aUnit.x + zoneDelta.x
|
||||
aUnit.y = aUnit.y + zoneDelta.z -- again!!!!
|
||||
@ -373,8 +418,11 @@ function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWayp
|
||||
loc.y = 0
|
||||
loc.z = firstPoint.y
|
||||
local bestAirbase = dcsCommon.getClosestAirbaseTo(loc)
|
||||
--departingAerodrome = bestAirbase
|
||||
firstPoint.airdromeId = bestAirbase:getID()
|
||||
-- trigger.action.outText("first: adjusted to " .. firstPoint.airdromeId, 30)
|
||||
departFromAerodrome = true
|
||||
fromParking = dcsCommon.stringStartsWith(firstPoint.action, "From Parking")
|
||||
end
|
||||
|
||||
-- adjust last point (landing)
|
||||
@ -393,8 +441,20 @@ function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWayp
|
||||
|
||||
end
|
||||
end
|
||||
end -- if theRoute
|
||||
|
||||
-- now process departing slot if given
|
||||
if departFromAerodrome then
|
||||
-- we may need alt from land to add here, maybe later
|
||||
|
||||
-- now process parking slots, and choose closest slot
|
||||
-- per unit's location
|
||||
if fromParking then
|
||||
cloneZones.assignClosestParking(theData)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function cloneZones.uniqueID()
|
||||
local uid = cloneZones.uniqueCounter
|
||||
cloneZones.uniqueCounter = cloneZones.uniqueCounter + 1
|
||||
@ -670,6 +730,9 @@ function cloneZones.handoffTracking(theGroup, theZone)
|
||||
end
|
||||
|
||||
function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
if cloneZones.verbose or spawnZone.verbose then
|
||||
trigger.action.outText("+++clnZ: spawning with template <" .. theZone.name .. "> for spawner <" .. spawnZone.name .. ">", 30)
|
||||
end
|
||||
-- theZone is the cloner with the template
|
||||
-- spawnZone is the spawner with settings
|
||||
-- if not spawnZone then spawnZone = theZone end
|
||||
@ -697,17 +760,34 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
local theCat = cfxMX.catText2ID(cat)
|
||||
rawData.CZtheCat = theCat -- save cat
|
||||
|
||||
-- update their position if not spawning to exact same location
|
||||
-- update their position if not spawning to exact same location
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: tmpl delta x = <" .. math.floor(zoneDelta.x) .. ">, y = <" .. math.floor(zoneDelta.z) .. "> for tmpl <" .. theZone.name .. "> to cloner <" .. spawnZone.name .. ">", 30)
|
||||
end
|
||||
cloneZones.updateLocationsInGroupData(rawData, zoneDelta, spawnZone.moveRoute)
|
||||
|
||||
-- apply randomizer if selected
|
||||
|
||||
if spawnZone.rndLoc then
|
||||
--trigger.action.outText("rndloc for <" .. spawnZone.name .. ">", 30)
|
||||
-- calculate the entire group's displacement
|
||||
local units = rawData.units
|
||||
local r = math.random() * spawnZone.radius
|
||||
local phi = 6.2831 * math.random() -- that's 2Pi, folx
|
||||
local dx = r * math.cos(phi)
|
||||
local dy = r * math.sin(phi)
|
||||
|
||||
for idx, aUnit in pairs(units) do
|
||||
local r = math.random() * spawnZone.radius
|
||||
local phi = 6.2831 * math.random() -- that's 2Pi, folx
|
||||
local dx = r * math.cos(phi)
|
||||
local dy = r * math.sin(phi)
|
||||
if not spawnZone.centerOnly then
|
||||
-- *every unit's displacement is randomized
|
||||
r = math.random() * spawnZone.radius
|
||||
phi = 6.2831 * math.random() -- that's 2Pi, folx
|
||||
dx = r * math.cos(phi)
|
||||
dy = r * math.sin(phi)
|
||||
end
|
||||
if spawnZone.verbose or cloneZones.verbose then
|
||||
trigger.action.outText("+++clnZ: <" .. spawnZone.name .. "> R = " .. spawnZone.radius .. ":G<" .. rawData.name .. "/" .. aUnit.name .. "> - rndLoc: r = " .. r .. ", dx = " .. dx .. ", dy= " .. dy .. ".", 30)
|
||||
end
|
||||
aUnit.x = aUnit.x + dx
|
||||
aUnit.y = aUnit.y + dy
|
||||
end
|
||||
@ -715,44 +795,71 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
|
||||
if spawnZone.rndHeading then
|
||||
local units = rawData.units
|
||||
for idx, aUnit in pairs(units) do
|
||||
local phi = 6.2831 * math.random() -- that's 2Pi, folx
|
||||
aUnit.heading = phi
|
||||
if spawnZone.centerOnly and units and units[1] then
|
||||
-- rotate entire group around unit 1
|
||||
local cx = units[1].x
|
||||
local cy = units[1].y
|
||||
local degrees = 360 * math.random() -- rotateGroupData uses degrees
|
||||
dcsCommon.rotateGroupData(rawData, degrees, cx, cy)
|
||||
else
|
||||
for idx, aUnit in pairs(units) do
|
||||
local phi = 6.2831 * math.random() -- that's 2Pi, folx
|
||||
aUnit.heading = phi
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- apply onRoad option if selected
|
||||
if spawnZone.onRoad then
|
||||
local units = rawData.units
|
||||
local iterCount = 0
|
||||
local otherLocs = {} -- resolved locs
|
||||
for idx, aUnit in pairs(units) do
|
||||
local cx = aUnit.x
|
||||
local cy = aUnit.y
|
||||
-- we now iterate until there is enough separation or too many iters
|
||||
local tooClose
|
||||
local np, nx, ny
|
||||
repeat
|
||||
nx, ny = land.getClosestPointOnRoads("roads", cx, cy)
|
||||
-- compare this with all other locs
|
||||
np = {x=nx, y=ny}
|
||||
tooClose = false
|
||||
for idc, op in pairs(otherLocs) do
|
||||
local d = dcsCommon.dist(np, op)
|
||||
if d < cloneZones.minSep then
|
||||
tooClose = true
|
||||
cx = cx + cloneZones.minSep
|
||||
cy = cy + cloneZones.minSep
|
||||
iterCount = iterCount + 1
|
||||
-- trigger.action.outText("d fail for <" .. aUnit.name.. ">: d= <" .. d .. ">, iters = <" .. iterCount .. ">", 30)
|
||||
end
|
||||
end
|
||||
until (iterCount > cloneZones.maxIter) or (not tooClose)
|
||||
-- trigger.action.outText("separation iters for <" .. aUnit.name.. ">:<" .. iterCount .. ">", 30)
|
||||
table.insert(otherLocs, np)
|
||||
aUnit.x = nx
|
||||
aUnit.y = ny
|
||||
end
|
||||
if spawnZone.centerOnly then
|
||||
-- only place the first unit in group on roads
|
||||
-- and displace all other with the same offset
|
||||
local hasOffset = false
|
||||
local dx, dy, cx, cy
|
||||
for idx, aUnit in pairs(units) do
|
||||
cx = aUnit.x
|
||||
cy = aUnit.y
|
||||
if not hasOffset then
|
||||
local nx, ny = land.getClosestPointOnRoads("roads", cx, cy)
|
||||
dx = nx - cx
|
||||
dy = ny - cy
|
||||
hasOffset = true
|
||||
end
|
||||
aUnit.x = cx + dx
|
||||
aUnit.y = cy + dy
|
||||
end
|
||||
else
|
||||
local iterCount = 0
|
||||
local otherLocs = {} -- resolved locs
|
||||
for idx, aUnit in pairs(units) do
|
||||
local cx = aUnit.x
|
||||
local cy = aUnit.y
|
||||
-- we now iterate until there is enough separation or too many iters
|
||||
local tooClose
|
||||
local np, nx, ny
|
||||
repeat
|
||||
nx, ny = land.getClosestPointOnRoads("roads", cx, cy)
|
||||
-- compare this with all other locs
|
||||
np = {x=nx, y=ny}
|
||||
tooClose = false
|
||||
for idc, op in pairs(otherLocs) do
|
||||
local d = dcsCommon.dist(np, op)
|
||||
if d < cloneZones.minSep then
|
||||
tooClose = true
|
||||
cx = cx + cloneZones.minSep
|
||||
cy = cy + cloneZones.minSep
|
||||
iterCount = iterCount + 1
|
||||
-- trigger.action.outText("d fail for <" .. aUnit.name.. ">: d= <" .. d .. ">, iters = <" .. iterCount .. ">", 30)
|
||||
end
|
||||
end
|
||||
until (iterCount > cloneZones.maxIter) or (not tooClose)
|
||||
-- trigger.action.outText("separation iters for <" .. aUnit.name.. ">:<" .. iterCount .. ">", 30)
|
||||
table.insert(otherLocs, np)
|
||||
aUnit.x = nx
|
||||
aUnit.y = ny
|
||||
end
|
||||
end -- else centerOnly
|
||||
end
|
||||
|
||||
|
||||
@ -946,6 +1053,7 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
|
||||
end
|
||||
|
||||
function cloneZones.spawnWithCloner(theZone)
|
||||
trigger.action.outText("+++clnZ: enter spawnWithCloner for <" .. theZone.name .. ">", 30)
|
||||
if not theZone then
|
||||
trigger.action.outText("+++clnZ: nil zone on spawnWithCloner", 30)
|
||||
return
|
||||
@ -966,14 +1074,17 @@ function cloneZones.spawnWithCloner(theZone)
|
||||
local templates = dcsCommon.splitString(templateName, ",")
|
||||
templateName = dcsCommon.pickRandom(templates)
|
||||
templateName = dcsCommon.trim(templateName)
|
||||
if cloneZones.verbose then
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: picked random template <" .. templateName .."> for from <" .. allNames .. "> for cloner " .. theZone.name, 30)
|
||||
end
|
||||
end
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: spawning - picked <" .. templateName .. "> as template", 30)
|
||||
end
|
||||
|
||||
local newTemplate = cloneZones.getCloneZoneByName(templateName)
|
||||
if not newTemplate then
|
||||
if cloneZones.verbose then
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++clnZ: no clone source with name <" .. templateName .."> for cloner " .. theZone.name, 30)
|
||||
end
|
||||
return
|
||||
@ -1118,10 +1229,10 @@ function cloneZones.update()
|
||||
end
|
||||
end
|
||||
|
||||
function cloneZones.onStart()
|
||||
--trigger.action.outText("+++clnZ: Enter atStart", 30)
|
||||
function cloneZones.doOnStart()
|
||||
for idx, theZone in pairs(cloneZones.cloners) do
|
||||
if theZone.onStart then
|
||||
trigger.action.outText("+++clnZ: onStart true for <" .. theZone.name .. ">", 30)
|
||||
if theZone.isStarted then
|
||||
if cloneZones.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++clnz: onStart pre-empted for <" .. theZone.name .. "> by persistence", 30)
|
||||
@ -1422,7 +1533,7 @@ function cloneZones.start()
|
||||
-- cycles to go through object removal
|
||||
-- persistencey has loaded isStarted if a cloner was
|
||||
-- already started
|
||||
timer.scheduleFunction(cloneZones.onStart, {}, timer.getTime() + 0.1)
|
||||
timer.scheduleFunction(cloneZones.doOnStart, {}, timer.getTime() + 1.0)
|
||||
|
||||
-- start update
|
||||
cloneZones.update()
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
dcsCommon = {}
|
||||
dcsCommon.version = "2.7.1"
|
||||
dcsCommon.version = "2.7.2"
|
||||
--[[-- VERSION HISTORY
|
||||
2.2.6 - compassPositionOfARelativeToB
|
||||
- clockPositionOfARelativeToB
|
||||
@ -52,7 +52,7 @@ dcsCommon.version = "2.7.1"
|
||||
- dcsCommon.trimArray(
|
||||
- createStaticObjectData uses trim for type
|
||||
- getEnemyCoalitionFor understands strings, still returns number
|
||||
- coalition2county also undertsands 'red' and 'blue'
|
||||
- coalition2county also understands 'red' and 'blue'
|
||||
2.5.0 - "Line" formation with one unit places unit at center
|
||||
2.5.1 - vNorm(a)
|
||||
2.5.1 - added SA-18 Igla manpad to unitIsInfantry()
|
||||
@ -92,6 +92,12 @@ dcsCommon.version = "2.7.1"
|
||||
2.7.1 - new isPlayerUnit() -- moved from cfxPlayer
|
||||
new getAllExistingPlayerUnitsRaw - from cfxPlayer
|
||||
new typeIsInfantry()
|
||||
2.7.2 - new rangeArrayFromString()
|
||||
fixed leading blank bug in flagArrayFromString
|
||||
new incFlag()
|
||||
new decFlag()
|
||||
nil trap in stringStartsWith()
|
||||
new getClosestFreeSlotForCatInAirbaseTo()
|
||||
|
||||
--]]--
|
||||
|
||||
@ -388,6 +394,64 @@ dcsCommon.version = "2.7.1"
|
||||
return closestBase, delta
|
||||
end
|
||||
|
||||
function dcsCommon.getClosestFreeSlotForCatInAirbaseTo(cat, x, y, theAirbase, ignore)
|
||||
if not theAirbase then return nil end
|
||||
if not ignore then ignore = {} end
|
||||
if not cat then return nil end
|
||||
if (not cat == "helicopter") and (not cat == "plane") then
|
||||
trigger.action.outText("+++common-getslotforcat: wrong cat <" .. cat .. ">", 30)
|
||||
return nil
|
||||
end
|
||||
local allFree = theAirbase:getParking(true) -- only free slots
|
||||
local filterFreeByType = {}
|
||||
for idx, aSlot in pairs(allFree) do
|
||||
local termT = aSlot.Term_Type
|
||||
if termT == 104 or
|
||||
(termT == 72 and cat == "plane") or
|
||||
(termT == 68 and cat == "plane") or
|
||||
(termT == 40 and cat == "helicopter") then
|
||||
table.insert(filterFreeByType, aSlot)
|
||||
else
|
||||
-- we skip this slot, not good for type
|
||||
end
|
||||
end
|
||||
|
||||
if #filterFreeByType == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
local reallyFree = {}
|
||||
for idx, aSlot in pairs(filterFreeByType) do
|
||||
local slotNum = aSlot.Term_Index
|
||||
isTaken = false
|
||||
for idy, taken in pairs(ignore) do
|
||||
if taken == slotNum then isTaken = true end
|
||||
end
|
||||
if not isTaken then
|
||||
table.insert(reallyFree, aSlot)
|
||||
end
|
||||
end
|
||||
|
||||
if #reallyFree < 1 then
|
||||
reallyFree = filterFreeByType
|
||||
end
|
||||
|
||||
local closestDist = math.huge
|
||||
local closestSlot = nil
|
||||
local p = {x = x, y = 0, z = y} -- !!
|
||||
for idx, aSlot in pairs(reallyFree) do
|
||||
local sp = {x = aSlot.vTerminalPos.x, y = 0, z = aSlot.vTerminalPos.z}
|
||||
local currDist = dcsCommon.distFlat(p, sp)
|
||||
--trigger.action.outText("slot <" .. aSlot.Term_Index .. "> has dist " .. math.floor(currDist) .. " and _0 of <" .. aSlot.Term_Index_0 .. ">", 30)
|
||||
if currDist < closestDist then
|
||||
closestSlot = aSlot
|
||||
closestDist = currDist
|
||||
end
|
||||
end
|
||||
--trigger.action.outText("slot <" .. closestSlot.Term_Index .. "> has closest dist <" .. math.floor(closestDist) .. ">", 30)
|
||||
return closestSlot
|
||||
end
|
||||
|
||||
--
|
||||
-- U N I T S M A N A G E M E N T
|
||||
--
|
||||
@ -1823,6 +1887,7 @@ end
|
||||
end
|
||||
|
||||
function dcsCommon.stringStartsWith(theString, thePrefix)
|
||||
if not theString then return false end
|
||||
return theString:find(thePrefix) == 1
|
||||
end
|
||||
|
||||
@ -2473,6 +2538,7 @@ function dcsCommon.flagArrayFromString(inString, verbose)
|
||||
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, "-")
|
||||
@ -2498,7 +2564,7 @@ function dcsCommon.flagArrayFromString(inString, verbose)
|
||||
end
|
||||
else
|
||||
-- single number
|
||||
f = dcsCommon.trim(anElement) -- DML flag upgrade: accept strings tonumber(anElement)
|
||||
local f = dcsCommon.trim(anElement) -- DML flag upgrade: accept strings tonumber(anElement)
|
||||
if f then
|
||||
table.insert(flags, f)
|
||||
|
||||
@ -2513,6 +2579,81 @@ function dcsCommon.flagArrayFromString(inString, verbose)
|
||||
return flags
|
||||
end
|
||||
|
||||
function dcsCommon.rangeArrayFromString(inString, verbose)
|
||||
if not verbose then verbose = false end
|
||||
|
||||
if verbose then
|
||||
trigger.action.outText("+++rangeArray: processing <" .. inString .. ">", 30)
|
||||
end
|
||||
|
||||
if string.len(inString) < 1 then
|
||||
trigger.action.outText("+++rangeArray: empty ranges", 30)
|
||||
return {}
|
||||
end
|
||||
|
||||
local ranges = {}
|
||||
local rawElements = dcsCommon.splitString(inString, ",")
|
||||
-- go over all elements
|
||||
for idx, anElement in pairs(rawElements) do
|
||||
anElement = dcsCommon.trim(anElement)
|
||||
local outRange = {}
|
||||
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 to ranges
|
||||
outRange[1] = lowerBound
|
||||
outRange[2] = upperBound
|
||||
table.insert(ranges, outRange)
|
||||
if verbose then
|
||||
trigger.action.outText("+++rangeArray: new range <" .. lowerBound .. "> to <" .. upperBound .. ">", 30)
|
||||
end
|
||||
else
|
||||
-- bounds illegal
|
||||
trigger.action.outText("+++rangeArray: ignored range <" .. anElement .. "> (range)", 30)
|
||||
end
|
||||
else
|
||||
-- single number
|
||||
local f = dcsCommon.trim(anElement)
|
||||
f = tonumber(f)
|
||||
if f then
|
||||
outRange[1] = f
|
||||
outRange[2] = f
|
||||
table.insert(ranges, outRange)
|
||||
if verbose then
|
||||
trigger.action.outText("+++rangeArray: new (single-val) range <" .. f .. "> to <" .. f .. ">", 30)
|
||||
end
|
||||
else
|
||||
trigger.action.outText("+++rangeArray: ignored element <" .. anElement .. "> (single)", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
if verbose then
|
||||
trigger.action.outText("+++rangeArray: <" .. #ranges .. "> ranges total", 30)
|
||||
end
|
||||
return ranges
|
||||
end
|
||||
|
||||
function dcsCommon.incFlag(flagName)
|
||||
local v = trigger.misc.getUserFlag(flagName)
|
||||
trigger.action.setUserFlag(flagName, v + 1)
|
||||
end
|
||||
|
||||
function dcsCommon.decFlag(flagName)
|
||||
local v = trigger.misc.getUserFlag(flagName)
|
||||
trigger.action.setUserFlag(flagName, v - 1)
|
||||
end
|
||||
|
||||
function dcsCommon.objectHandler(theObject, theCollector)
|
||||
table.insert(theCollector, theObject)
|
||||
return true
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
groupTracker = {}
|
||||
groupTracker.version = "1.2.0"
|
||||
groupTracker.version = "1.2.1"
|
||||
groupTracker.verbose = false
|
||||
groupTracker.ups = 1
|
||||
groupTracker.requiredLibs = {
|
||||
@ -24,10 +24,11 @@ groupTracker.trackers = {}
|
||||
- allGone! output
|
||||
- triggerMethod
|
||||
- method
|
||||
- isDead optimization
|
||||
- isDead optimiz ation
|
||||
1.2.0 - double detection
|
||||
- numUnits output
|
||||
- persistence
|
||||
1.2.1 - allGone! bug removed
|
||||
|
||||
--]]--
|
||||
|
||||
@ -362,7 +363,7 @@ function groupTracker.update()
|
||||
-- see if we need to bang on empty!
|
||||
local currCount = #theZone.trackedGroups
|
||||
if theZone.allGoneFlag and currCount == 0 and currCount ~= theZone.lastGroupCount then
|
||||
cfxZones.pollFlag(aZone.allGoneFlag, aZone.trackerMethod, aZone)
|
||||
cfxZones.pollFlag(theZone.allGoneFlag, theZone.trackerMethod, theZone)
|
||||
end
|
||||
theZone.lastGroupCount = currCount
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
messenger = {}
|
||||
messenger.version = "1.3.3"
|
||||
messenger.version = "2.0.0"
|
||||
messenger.verbose = false
|
||||
messenger.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -26,6 +26,22 @@ messenger.messengers = {}
|
||||
- can interpret <lat>, <lon>, <mgrs>
|
||||
- zone-local verbosity
|
||||
1.3.3 - mute/messageMute option to start messenger in mute
|
||||
2.0.0 - re-engineered message wildcards
|
||||
- corrected dynamic content for time and latlon (classic)
|
||||
- new timeFormat attribute
|
||||
- <v: flagname>
|
||||
- <t: flagname>
|
||||
- added <ele>
|
||||
- added imperial
|
||||
- <lat: unit/zone>
|
||||
- <lon: unit/zone>
|
||||
- <ele: unit/zone>
|
||||
- <mgrs: unit/zone>
|
||||
- <latlon: unit/zone>
|
||||
- <lle: unit/zone>
|
||||
- messageError
|
||||
- unit
|
||||
- group
|
||||
|
||||
--]]--
|
||||
|
||||
@ -48,6 +64,7 @@ end
|
||||
-- read attributes
|
||||
--
|
||||
function messenger.preProcMessage(inMsg, theZone)
|
||||
-- Replace STATIC bits of message like CR and zone name
|
||||
if not inMsg then return "<nil inMsg>" end
|
||||
local formerType = type(inMsg)
|
||||
if formerType ~= "string" then inMsg = tostring(inMsg) end
|
||||
@ -58,27 +75,172 @@ function messenger.preProcMessage(inMsg, theZone)
|
||||
if theZone then
|
||||
outMsg = outMsg:gsub("<z>", theZone.name)
|
||||
end
|
||||
return outMsg
|
||||
end
|
||||
|
||||
-- Old-school processing to replace wildcards
|
||||
-- repalce <t> with current time
|
||||
-- replace <lat> with zone's current lonlat
|
||||
-- replace <mgrs> with zone's current mgrs
|
||||
function messenger.dynamicProcessClassic(inMsg, theZone)
|
||||
|
||||
if not inMsg then return "<nil inMsg>" end
|
||||
-- replace <t> with current mission time HMS
|
||||
local absSecs = timer.getAbsTime()-- + env.mission.start_time
|
||||
while absSecs > 86400 do
|
||||
absSecs = absSecs - 86400 -- subtract out all days
|
||||
end
|
||||
local timeString = dcsCommon.processHMS("<:h>:<:m>:<:s>", absSecs)
|
||||
outMsg = outMsg:gsub("<t>", timeString)
|
||||
local timeString = dcsCommon.processHMS(theZone.msgTimeFormat, absSecs)
|
||||
local outMsg = inMsg:gsub("<t>", timeString)
|
||||
|
||||
-- replace <lat> with lat of zone point and <lon> with lon of zone point
|
||||
-- and <mgrs> with mgrs coords of zone point
|
||||
local currPoint = cfxZones.getPoint(theZone)
|
||||
local lat, lon, alt = coord.LOtoLL(currPoint)
|
||||
local lat, lon = coord.LOtoLL(currPoint)
|
||||
lat, lon = dcsCommon.latLon2Text(lat, lon)
|
||||
local alt = land.getHeight({x = currPoint.x, y = currPoint.z})
|
||||
if theZone.imperialUnits then
|
||||
alt = math.floor(alt * 3.28084) -- feet
|
||||
else
|
||||
alt = math.floor(alt) -- meters
|
||||
end
|
||||
outMsg = outMsg:gsub("<lat>", lat)
|
||||
outMsg = outMsg:gsub("<lon>", lon)
|
||||
outMsg = outMsg:gsub("<ele>", alt)
|
||||
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
|
||||
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
|
||||
outMsg = outMsg:gsub("<mgrs>", mgrs)
|
||||
return outMsg
|
||||
end
|
||||
|
||||
--
|
||||
-- new dynamic flag processing
|
||||
--
|
||||
function messenger.processDynamicValues(inMsg, theZone)
|
||||
-- replace all occurences of <v: flagName> with their values
|
||||
local pattern = "<v:%s*[%s%w%*%d%.%-_]+>" -- no list allowed but blanks and * and . and - and _ --> we fail on the other specials to keep this simple
|
||||
local outMsg = inMsg
|
||||
repeat -- iterate all patterns one by one
|
||||
local startLoc, endLoc = string.find(outMsg, pattern)
|
||||
if startLoc then
|
||||
local theValParam = string.sub(outMsg, startLoc, endLoc)
|
||||
-- strip lead and trailer
|
||||
local param = string.gsub(theValParam, "<v:%s*", "")
|
||||
param = string.gsub(param, ">","")
|
||||
-- param = dcsCommon.trim(param) -- trim is called anyway
|
||||
-- access flag
|
||||
local val = cfxZones.getFlagValue(param, theZone)
|
||||
val = tostring(val)
|
||||
if not val then val = "NULL" end
|
||||
-- replace pattern in original with new val
|
||||
outMsg = string.gsub(outMsg, pattern, val, 1) -- only one sub!
|
||||
end
|
||||
until not startLoc
|
||||
return outMsg
|
||||
end
|
||||
|
||||
function messenger.processDynamicTime(inMsg, theZone)
|
||||
-- replace all occurences of <v: flagName> with their values
|
||||
local pattern = "<t:%s*[%s%w%*%d%.%-_]+>" -- no list allowed but blanks and * and . and - and _ --> we fail on the other specials to keep this simple
|
||||
local outMsg = inMsg
|
||||
repeat -- iterate all patterns one by one
|
||||
local startLoc, endLoc = string.find(outMsg, pattern)
|
||||
if startLoc then
|
||||
local theValParam = string.sub(outMsg, startLoc, endLoc)
|
||||
-- strip lead and trailer
|
||||
local param = string.gsub(theValParam, "<t:%s*", "")
|
||||
param = string.gsub(param, ">","")
|
||||
-- access flag
|
||||
local val = cfxZones.getFlagValue(param, theZone)
|
||||
-- use this to process as time value
|
||||
--trigger.action.outText("time: accessing <" .. param .. "> and received <" .. val .. ">", 30)
|
||||
local timeString = dcsCommon.processHMS(theZone.msgTimeFormat, val)
|
||||
|
||||
if not timeString then timeString = "NULL" end
|
||||
-- replace pattern in original with new val
|
||||
outMsg = string.gsub(outMsg, pattern, timeString, 1) -- only one sub!
|
||||
end
|
||||
until not startLoc
|
||||
return outMsg
|
||||
end
|
||||
|
||||
function messenger.processDynamicLoc(inMsg, theZone)
|
||||
-- replace all occurences of <lat/lon/ele/mgrs: flagName> with their values
|
||||
local locales = {"lat", "lon", "ele", "mgrs", "lle", "latlon"}
|
||||
local outMsg = inMsg
|
||||
for idx, aLocale in pairs(locales) do
|
||||
local pattern = "<" .. aLocale .. ":%s*[%s%w%*%d%.%-_]+>"
|
||||
repeat -- iterate all patterns one by one
|
||||
local startLoc, endLoc = string.find(outMsg, pattern)
|
||||
if startLoc then
|
||||
local theValParam = string.sub(outMsg, startLoc, endLoc)
|
||||
-- strip lead and trailer
|
||||
local param = string.gsub(theValParam, "<" .. aLocale .. ":%s*", "")
|
||||
param = string.gsub(param, ">","")
|
||||
-- find zone or unit
|
||||
param = dcsCommon.trim(param)
|
||||
local thePoint = nil
|
||||
local tZone = cfxZones.getZoneByName(param)
|
||||
local tUnit = Unit.getByName(param)
|
||||
if tZone then
|
||||
thePoint = cfxZones.getPoint(tZone)
|
||||
-- since zones always have elevation of 0,
|
||||
-- now get the elevation from the map
|
||||
thePoint.y = land.getHeight({x = thePoint.x, y = thePoint.z})
|
||||
elseif tUnit then
|
||||
if Unit.isExist(tUnit) then
|
||||
thePoint = tUnit:getPoint()
|
||||
end
|
||||
else
|
||||
-- nothing to do, remove me.
|
||||
end
|
||||
|
||||
local locString = theZone.errString
|
||||
if thePoint then
|
||||
-- now that we have a point, we can do locale-specific
|
||||
-- processing. return result in locString
|
||||
local lat, lon, alt = coord.LOtoLL(thePoint)
|
||||
lat, lon = dcsCommon.latLon2Text(lat, lon)
|
||||
if theZone.imperialUnits then
|
||||
alt = math.floor(alt * 3.28084) -- feet
|
||||
else
|
||||
alt = math.floor(alt) -- meters
|
||||
end
|
||||
if aLocale == "lat" then locString = lat
|
||||
elseif aLocale == "lon" then locString = lon
|
||||
elseif aLocale == "ele" then locString = tostring(alt)
|
||||
elseif aLocale == "lle" then locString = lat .. " " .. lon .. " ele " .. tostring(alt)
|
||||
elseif aLocale == "latlon" then locString = lat .. " " .. lon
|
||||
else
|
||||
-- we have mgrs
|
||||
local grid = coord.LLtoMGRS(coord.LOtoLL(thePoint))
|
||||
locString = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
|
||||
end
|
||||
end
|
||||
-- replace pattern in original with new val
|
||||
outMsg = string.gsub(outMsg, pattern, locString, 1) -- only one sub!
|
||||
end -- if startloc
|
||||
until not startLoc
|
||||
end -- for all locales
|
||||
return outMsg
|
||||
end
|
||||
|
||||
function messenger.dynamicFlagProcessing(inMsg, theZone)
|
||||
if not inMsg then return "No in message" end
|
||||
if not theZone then return "Nil zone" end
|
||||
|
||||
-- process <v: xxx>
|
||||
local msg = messenger.processDynamicValues(inMsg, theZone)
|
||||
|
||||
-- process <t: xxx>
|
||||
msg = messenger.processDynamicTime(msg, theZone)
|
||||
|
||||
-- process lat / lon / ele / mgrs
|
||||
msg = messenger.processDynamicLoc(msg, theZone)
|
||||
|
||||
return msg
|
||||
end
|
||||
|
||||
function messenger.createMessengerWithZone(theZone)
|
||||
-- start val - a range
|
||||
|
||||
@ -147,6 +309,7 @@ function messenger.createMessengerWithZone(theZone)
|
||||
theZone.lastMessageOn = cfxZones.getFlagValue(theZone.messageOnFlag, theZone)
|
||||
end
|
||||
|
||||
-- reveiver: coalition, group, unit
|
||||
if cfxZones.hasProperty(theZone, "coalition") then
|
||||
theZone.msgCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0)
|
||||
end
|
||||
@ -155,11 +318,45 @@ function messenger.createMessengerWithZone(theZone)
|
||||
theZone.msgCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "msgCoalition", 0)
|
||||
end
|
||||
|
||||
-- flag whose value can be read
|
||||
if cfxZones.hasProperty(theZone, "group") then
|
||||
theZone.msgGroup = cfxZones.getStringFromZoneProperty(theZone, "group", "<none>")
|
||||
end
|
||||
if cfxZones.hasProperty(theZone, "msgGroup") then
|
||||
theZone.msgGroup = cfxZones.getStringFromZoneProperty(theZone, "msgGroup", "<none>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "unit") then
|
||||
theZone.msgUnit = cfxZones.getStringFromZoneProperty(theZone, "unit", "<none>")
|
||||
end
|
||||
if cfxZones.hasProperty(theZone, "msgUnit") then
|
||||
theZone.msgUnit = cfxZones.getStringFromZoneProperty(theZone, "msgUnit", "<none>")
|
||||
end
|
||||
|
||||
if (theZone.msgGroup and theZone.msgUnit) or
|
||||
(theZone.msgGroup and theZone.msgCoalition) or
|
||||
(theZone.msgUnit and theZone.msgCoalition)
|
||||
then
|
||||
trigger.action.outText("+++msg: WARNING - messenger in <" .. theZone.name .. "> has conflicting coalition, group and unit, use only one.", 30)
|
||||
end
|
||||
|
||||
-- flag whose value can be read: to be deprecated
|
||||
if cfxZones.hasProperty(theZone, "messageValue?") then
|
||||
theZone.messageValue = cfxZones.getStringFromZoneProperty(theZone, "messageValue?", "<none>")
|
||||
end
|
||||
|
||||
-- time format for new <t: flagname>
|
||||
theZone.msgTimeFormat = cfxZones.getStringFromZoneProperty(theZone, "timeFormat", "<:h>:<:m>:<:s>")
|
||||
|
||||
theZone.imperialUnits = cfxZones.getBoolFromZoneProperty(theZone, "imperial", false)
|
||||
if cfxZones.hasProperty(theZone, "imperialUnits") then
|
||||
theZone.imperialUnits = cfxZones.getBoolFromZoneProperty(theZone, "imperialUnits", false)
|
||||
end
|
||||
|
||||
theZone.errString = cfxZones.getStringFromZoneProperty(theZone, "error", "")
|
||||
if cfxZones.hasProperty(theZone, "messageError") then
|
||||
theZone.errString = cfxZones.getStringFromZoneProperty(theZone, "messageError", "")
|
||||
end
|
||||
|
||||
if messenger.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++Msg: new zone <".. theZone.name .."> will say <".. theZone.message .. ">", 30)
|
||||
end
|
||||
@ -182,12 +379,22 @@ function messenger.getMessage(theZone)
|
||||
|
||||
|
||||
-- replace *zone and *value wildcards
|
||||
msg = string.gsub(msg, "*name", zName)
|
||||
msg = string.gsub(msg, "*value", zVal)
|
||||
--msg = string.gsub(msg, "*name", zName)-- deprecated
|
||||
--msg = string.gsub(msg, "*value", zVal) -- deprecated
|
||||
-- old-school <v> to provide value from messageValue
|
||||
msg = string.gsub(msg, "<v>", zVal)
|
||||
local z = tonumber(zVal)
|
||||
if not z then z = 0 end
|
||||
msg = dcsCommon.processHMS(msg, z)
|
||||
|
||||
-- process <t> [classic format], <latlon> and <mrgs>
|
||||
msg = messenger.dynamicProcessClassic(msg, theZone)
|
||||
|
||||
-- now add new processing of <x: flagname> access
|
||||
msg = messenger.dynamicFlagProcessing(msg, theZone)
|
||||
|
||||
-- now add new processinf of <lat: flagname>
|
||||
-- also handles <lon:x>, <ele:x>, <mgrs:x>
|
||||
return msg
|
||||
end
|
||||
|
||||
@ -212,6 +419,20 @@ function messenger.isTriggered(theZone)
|
||||
if theZone.msgCoalition then
|
||||
trigger.action.outTextForCoalition(theZone.msgCoalition, msg, theZone.duration, theZone.clearScreen)
|
||||
trigger.action.outSoundForCoalition(theZone.msgCoalition, fileName)
|
||||
elseif theZone.msgGroup then
|
||||
local theGroup = Group.getByName(theZone.msgGroup)
|
||||
if theGroup and Group.isExist(theGroup) then
|
||||
local ID = theGroup:getID()
|
||||
trigger.action.outTextForGroup(ID, msg, theZone.duration, theZone.clearScreen)
|
||||
trigger.action.outSoundForGroup(ID, fileName)
|
||||
end
|
||||
elseif theZone.msgUnit then
|
||||
local theUnit = Unit.getByName(theZone.msgUnit)
|
||||
if theUnit and Unit.isExist(theUnit) then
|
||||
local ID = theUnit:getID()
|
||||
trigger.action.outTextForUnit(ID, msg, theZone.duration, theZone.clearScreen)
|
||||
trigger.action.outSoundForUnit(ID, fileName)
|
||||
end
|
||||
else
|
||||
-- out to all
|
||||
trigger.action.outText(msg, theZone.duration, theZone.clearScreen)
|
||||
@ -312,5 +533,10 @@ end
|
||||
|
||||
--[[--
|
||||
Wildcard extension:
|
||||
messageValue supports multiple flags like 1-3, *hi ther, *bingo and then *value[name] returns that value
|
||||
- general flag access <v flag name>
|
||||
- <t: flag name>
|
||||
- <lat: unit/zone name>
|
||||
- <mrgs: unit/zone name>
|
||||
|
||||
|
||||
--]]--
|
||||
@ -1,5 +1,5 @@
|
||||
radioMenu = {}
|
||||
radioMenu.version = "1.1.0"
|
||||
radioMenu.version = "2.0.0"
|
||||
radioMenu.verbose = false
|
||||
radioMenu.ups = 1
|
||||
radioMenu.requiredLibs = {
|
||||
@ -15,8 +15,18 @@ radioMenu.menus = {}
|
||||
1.1.0 removeMenu
|
||||
addMenu
|
||||
menuVisible
|
||||
2.0.0 redesign: handles multiple receivers
|
||||
optional MX module
|
||||
group option
|
||||
type option
|
||||
multiple group names
|
||||
multiple types
|
||||
gereric helo type
|
||||
generic plane type
|
||||
type works with coalition
|
||||
|
||||
--]]--
|
||||
|
||||
function radioMenu.addRadioMenu(theZone)
|
||||
table.insert(radioMenu.menus, theZone)
|
||||
end
|
||||
@ -35,44 +45,192 @@ end
|
||||
--
|
||||
-- read zone
|
||||
--
|
||||
function radioMenu.installMenu(theZone)
|
||||
if theZone.coalition == 0 then
|
||||
theZone.rootMenu = missionCommands.addSubMenu(theZone.rootName, nil)
|
||||
function radioMenu.filterPlayerIDForType(theZone)
|
||||
-- note: we currently ignore coalition
|
||||
local theIDs = {}
|
||||
local allTypes = {}
|
||||
if dcsCommon.containsString(theZone.menuTypes, ",") then
|
||||
allTypes = dcsCommon.splitString(theZone.menuTypes, ",")
|
||||
else
|
||||
theZone.rootMenu = missionCommands.addSubMenuForCoalition(theZone.coalition, theZone.rootName, nil)
|
||||
table.insert(allTypes, theZone.menuTypes)
|
||||
end
|
||||
|
||||
local menuA = cfxZones.getStringFromZoneProperty(theZone, "itemA", "<no A submenu>")
|
||||
if theZone.coalition == 0 then
|
||||
theZone.menuA = missionCommands.addCommand(menuA, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "A"})
|
||||
-- now iterate all types, and include any player that matches
|
||||
-- note that a player may match twice, so we use a dict instead of an
|
||||
-- array. Since we later iterate ID by idx, that's not an issue
|
||||
|
||||
for idx, aType in pairs(allTypes) do
|
||||
local theType = dcsCommon.trim(aType)
|
||||
local lowerType = string.lower(theType)
|
||||
|
||||
for gName, gData in pairs(cfxMX.playerGroupByName) do
|
||||
-- get coalition of group
|
||||
local coa = cfxMX.groupCoalitionByName[gName]
|
||||
if (theZone.coalition == 0 or theZone.coalition == coa) then
|
||||
-- do special types first
|
||||
if dcsCommon.stringStartsWith(lowerType, "helo") or dcsCommon.stringStartsWith(lowerType, "heli") then
|
||||
-- we look for all helicoperts
|
||||
if cfxMX.groupTypeByName[gName] == "helicopter" then
|
||||
theIDs[gName] = gData.groupId
|
||||
if theZone.verbose or radioMenu.verbose then
|
||||
trigger.action.outText("+++menu: Player Group <" .. gName .. "> matches gen-type helicopter", 30)
|
||||
end
|
||||
end
|
||||
elseif lowerType == "plane" or lowerType == "planes" then
|
||||
-- we look for all planes
|
||||
if cfxMX.groupTypeByName[gName] == "plane" then
|
||||
theIDs[gName] = gData.groupId
|
||||
if theZone.verbose or radioMenu.verbose then
|
||||
trigger.action.outText("+++menu: Player Group <" .. gName .. "> matches gen-type plane", 30)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- we are looking for a particular type, e.g. A-10A
|
||||
-- since groups do not carry the type, but all player
|
||||
-- groups are of the same type, we access the first
|
||||
-- unit. Note that this may later break if ED implement
|
||||
-- player groups of mixed type
|
||||
if gData.units and gData.units[1] and gData.units[1].type == theType then
|
||||
theIDs[gName] = gData.groupId
|
||||
if theZone.verbose or radioMenu.verbose then
|
||||
trigger.action.outText("+++menu: Player Group <" .. gName .. "> matches type <" .. theType .. ">", 30)
|
||||
end
|
||||
else
|
||||
|
||||
end
|
||||
end
|
||||
else
|
||||
if theZone.verbose or radioMenu.verbose then
|
||||
trigger.action.outText("+++menu: type check failed coalition for <" .. gName .. ">", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return theIDs
|
||||
end
|
||||
|
||||
function radioMenu.filterPlayerIDForGroup(theZone)
|
||||
-- create an iterable list of groups, separated by commas
|
||||
-- note that we could introduce wildcards for groups later
|
||||
local theIDs = {}
|
||||
local allGroups = {}
|
||||
if dcsCommon.containsString(theZone.menuGroup, ",") then
|
||||
allGroups = dcsCommon.splitString(theZone.menuGroup, ",")
|
||||
else
|
||||
theZone.menuA = missionCommands.addCommandForCoalition(theZone.coalition, menuA, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "A"})
|
||||
table.insert(allGroups, theZone.menuGroup)
|
||||
end
|
||||
|
||||
for idx, gName in pairs(allGroups) do
|
||||
gName = dcsCommon.trim(gName)
|
||||
local theGroup = cfxMX.playerGroupByName[gName]
|
||||
if theGroup then
|
||||
local gID = theGroup.groupId
|
||||
table.insert(theIDs, gID)
|
||||
if theZone.verbose or radioMenu.verbose then
|
||||
trigger.action.outText("+++menu: Player Group <" .. gName .. "> found: <" .. gID .. ">", 30)
|
||||
end
|
||||
else
|
||||
trigger.action.outText("+++menu: Player Group <" .. gName .. "> does not exist", 30)
|
||||
end
|
||||
end
|
||||
|
||||
return theIDs
|
||||
end
|
||||
|
||||
function radioMenu.installMenu(theZone)
|
||||
-- local theGroup = 0 -- was: nil
|
||||
local gID = nil
|
||||
if theZone.menuGroup then
|
||||
if not cfxMX then
|
||||
trigger.action.outText("WARNING: radioMenu's group attribute requires the 'cfxMX' module", 30)
|
||||
return
|
||||
end
|
||||
-- access cfxMX player info for group ID
|
||||
gID = radioMenu.filterPlayerIDForGroup(theZone)
|
||||
elseif theZone.menuTypes then
|
||||
if not cfxMX then
|
||||
trigger.action.outText("WARNING: radioMenu's type attribute requires the 'cfxMX' module", 30)
|
||||
return
|
||||
end
|
||||
-- access cxfMX player infor with type match for ID
|
||||
gID = radioMenu.filterPlayerIDForType(theZone)
|
||||
end
|
||||
|
||||
theZone.rootMenu = {}
|
||||
theZone.mcdA = {}
|
||||
theZone.mcdB = {}
|
||||
theZone.mcdC = {}
|
||||
theZone.mcdD = {}
|
||||
theZone.mcdA[0] = 0
|
||||
theZone.mcdB[0] = 0
|
||||
theZone.mcdC[0] = 0
|
||||
theZone.mcdD[0] = 0
|
||||
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
for idx, grp in pairs(gID) do
|
||||
local aRoot = missionCommands.addSubMenuForGroup(grp, theZone.rootName, nil)
|
||||
theZone.rootMenu[grp] = aRoot
|
||||
theZone.mcdA[grp] = 0
|
||||
theZone.mcdB[grp] = 0
|
||||
theZone.mcdC[grp] = 0
|
||||
theZone.mcdD[grp] = 0
|
||||
end
|
||||
elseif theZone.coalition == 0 then
|
||||
theZone.rootMenu[0] = missionCommands.addSubMenu(theZone.rootName, nil)
|
||||
else
|
||||
theZone.rootMenu[0] = missionCommands.addSubMenuForCoalition(theZone.coalition, theZone.rootName, nil)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "itemA") then
|
||||
local menuA = cfxZones.getStringFromZoneProperty(theZone, "itemA", "<no A submenu>")
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
theZone.menuA = {}
|
||||
for idx, grp in pairs(gID) do
|
||||
theZone.menuA[grp] = missionCommands.addCommandForGroup(grp, menuA, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "A", grp})
|
||||
end
|
||||
elseif theZone.coalition == 0 then
|
||||
theZone.menuA = missionCommands.addCommand(menuA, theZone.rootMenu[0], radioMenu.redirectMenuX, {theZone, "A"})
|
||||
else
|
||||
theZone.menuA = missionCommands.addCommandForCoalition(theZone.coalition, menuA, theZone.rootMenu[0], radioMenu.redirectMenuX, {theZone, "A"})
|
||||
end
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "itemB") then
|
||||
local menuB = cfxZones.getStringFromZoneProperty(theZone, "itemB", "<no B submenu>")
|
||||
if theZone.coalition == 0 then
|
||||
theZone.menuB = missionCommands.addCommand(menuB, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "B"})
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
for idx, grp in pairs(gID) do
|
||||
theZone.menuB[grp] = missionCommands.addCommandForGroup(grp, menuB, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "B"})
|
||||
end
|
||||
elseif theZone.coalition == 0 then
|
||||
theZone.menuB = missionCommands.addCommand(menuB, theZone.rootMenu[0], radioMenu.redirectMenuX, {theZone, "B"})
|
||||
else
|
||||
theZone.menuB = missionCommands.addCommandForCoalition(theZone.coalition, menuB, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "B"})
|
||||
theZone.menuB = missionCommands.addCommandForCoalition(theZone.coalition, menuB, theZone.rootMenu[0], radioMenu.redirectMenuX, {theZone, "B"})
|
||||
end
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "itemC") then
|
||||
local menuC = cfxZones.getStringFromZoneProperty(theZone, "itemC", "<no C submenu>")
|
||||
if theZone.coalition == 0 then
|
||||
theZone.menuC = missionCommands.addCommand(menuC, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "C"})
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
for idx, grp in pairs(gID) do
|
||||
theZone.menuC[grp] = missionCommands.addCommandForGroup(grp, menuC, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "C"})
|
||||
end
|
||||
elseif theZone.coalition == 0 then
|
||||
theZone.menuC = missionCommands.addCommand(menuC, theZone.rootMenu[0], radioMenu.redirectMenuX, {theZone, "C"})
|
||||
else
|
||||
theZone.menuC = missionCommands.addCommandForCoalition(theZone.coalition, menuC, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "C"})
|
||||
theZone.menuC = missionCommands.addCommandForCoalition(theZone.coalition, menuC, theZone.rootMenu[0], radioMenu.redirectMenuX, {theZone, "C"})
|
||||
end
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "itemD") then
|
||||
local menuD = cfxZones.getStringFromZoneProperty(theZone, "itemD", "<no D submenu>")
|
||||
if theZone.coalition == 0 then
|
||||
theZone.menuD = missionCommands.addCommand(menuD, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "D"})
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
for idx, grp in pairs(gID) do
|
||||
theZone.menuD[grp] = missionCommands.addCommandForGroup(grp, menuD, theZone.rootMenu[grp], radioMenu.redirectMenuX, {theZone, "D"})
|
||||
end
|
||||
elseif theZone.coalition == 0 then
|
||||
theZone.menuD = missionCommands.addCommand(menuD, theZone.rootMenu[0], radioMenu.redirectMenuX, {theZone, "D"})
|
||||
else
|
||||
theZone.menuD = missionCommands.addCommandForCoalition(theZone.coalition, menuD, theZone.rootMenu, radioMenu.redirectMenuX, {theZone, "D"})
|
||||
theZone.menuD = missionCommands.addCommandForCoalition(theZone.coalition, menuD, theZone.rootMenu[0], radioMenu.redirectMenuX, {theZone, "D"})
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -81,6 +239,18 @@ function radioMenu.createRadioMenuWithZone(theZone)
|
||||
theZone.rootName = cfxZones.getStringFromZoneProperty(theZone, "radioMenu", "<No Name>")
|
||||
|
||||
theZone.coalition = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0)
|
||||
-- groups / types
|
||||
if cfxZones.hasProperty(theZone, "group") then
|
||||
theZone.menuGroup = cfxZones.getStringFromZoneProperty(theZone, "group", "<none>")
|
||||
theZone.menuGroup = dcsCommon.trim(theZone.menuGroup)
|
||||
elseif cfxZones.hasProperty(theZone, "groups") then
|
||||
theZone.menuGroup = cfxZones.getStringFromZoneProperty(theZone, "groups", "<none>")
|
||||
theZone.menuGroup = dcsCommon.trim(theZone.menuGroup)
|
||||
elseif cfxZones.hasProperty(theZone, "type") then
|
||||
theZone.menuTypes = cfxZones.getStringFromZoneProperty(theZone, "type", "none")
|
||||
elseif cfxZones.hasProperty(theZone, "types") then
|
||||
theZone.menuTypes = cfxZones.getStringFromZoneProperty(theZone, "types", "none")
|
||||
end
|
||||
|
||||
theZone.menuVisible = cfxZones.getBoolFromZoneProperty(theZone, "menuVisible", true)
|
||||
|
||||
@ -99,22 +269,22 @@ function radioMenu.createRadioMenuWithZone(theZone)
|
||||
|
||||
theZone.itemAChosen = cfxZones.getStringFromZoneProperty(theZone, "A!", "*<none>")
|
||||
theZone.cooldownA = cfxZones.getNumberFromZoneProperty(theZone, "cooldownA", 0)
|
||||
theZone.mcdA = 0
|
||||
--theZone.mcdA = 0
|
||||
theZone.busyA = cfxZones.getStringFromZoneProperty(theZone, "busyA", "Please stand by (<s> seconds)")
|
||||
|
||||
theZone.itemBChosen = cfxZones.getStringFromZoneProperty(theZone, "B!", "*<none>")
|
||||
theZone.cooldownB = cfxZones.getNumberFromZoneProperty(theZone, "cooldownB", 0)
|
||||
theZone.mcdB = 0
|
||||
--theZone.mcdB = 0
|
||||
theZone.busyB = cfxZones.getStringFromZoneProperty(theZone, "busyB", "Please stand by (<s> seconds)")
|
||||
|
||||
theZone.itemCChosen = cfxZones.getStringFromZoneProperty(theZone, "C!", "*<none>")
|
||||
theZone.cooldownC = cfxZones.getNumberFromZoneProperty(theZone, "cooldownC", 0)
|
||||
theZone.mcdC = 0
|
||||
--theZone.mcdC = 0
|
||||
theZone.busyC = cfxZones.getStringFromZoneProperty(theZone, "busyC", "Please stand by (<s> seconds)")
|
||||
|
||||
theZone.itemDChosen = cfxZones.getStringFromZoneProperty(theZone, "D!", "*<none>")
|
||||
theZone.cooldownD = cfxZones.getNumberFromZoneProperty(theZone, "cooldownD", 0)
|
||||
theZone.mcdD = 0
|
||||
--theZone.mcdD = 0
|
||||
theZone.busyD = cfxZones.getStringFromZoneProperty(theZone, "busyD", "Please stand by (<s> seconds)")
|
||||
|
||||
if cfxZones.hasProperty(theZone, "removeMenu?") then
|
||||
@ -160,24 +330,43 @@ function radioMenu.redirectMenuX(args)
|
||||
timer.scheduleFunction(radioMenu.doMenuX, args, timer.getTime() + 0.1)
|
||||
end
|
||||
|
||||
function radioMenu.cdByGID(cd, theZone, gID)
|
||||
if not gID then gID = 0 end
|
||||
--if not gID then return cd[0] end
|
||||
return cd[gID]
|
||||
end
|
||||
|
||||
function radioMenu.setCDByGID(cd, theZone, gID, newVal)
|
||||
if not gID then gID = 0 end
|
||||
--theZone[cd] = newVal
|
||||
--
|
||||
--end
|
||||
local allCD = theZone[cd]
|
||||
allCD[gID] = newVal
|
||||
theZone[cd] = allCD
|
||||
end
|
||||
|
||||
function radioMenu.doMenuX(args)
|
||||
theZone = args[1]
|
||||
theItemIndex = args[2] -- A, B , C .. ?
|
||||
local cd = theZone.mcdA
|
||||
theGroup = args[3] -- can be nil or groupID
|
||||
if not theGroup then theGroup = 0 end
|
||||
|
||||
local cd = radioMenu.cdByGID(theZone.mcdA, theZone, theGroup) --theZone.mcdA
|
||||
local busy = theZone.busyA
|
||||
local theFlag = theZone.itemAChosen
|
||||
|
||||
-- decode A..X
|
||||
if theItemIndex == "B"then
|
||||
cd = theZone.mcdB
|
||||
cd = radioMenu.cdByGID(theZone.mcdB, theZone, theGroup) -- theZone.mcdB
|
||||
busy = theZone.busyB
|
||||
theFlag = theZone.itemBChosen
|
||||
elseif theItemIndex == "C" then
|
||||
cd = theZone.mcdC
|
||||
cd = radioMenu.cdByGID(theZone.mcdC, theZone, theGroup) -- theZone.mcdC
|
||||
busy = theZone.busyC
|
||||
theFlag = theZone.itemCChosen
|
||||
elseif theItemIndex == "D" then
|
||||
cd = theZone.mcdD
|
||||
cd = radioMenu.cdByGID(theZone.mcdD, theZone, theGroup) -- theZone.mcdD
|
||||
busy = theZone.busyD
|
||||
theFlag = theZone.itemDChosen
|
||||
end
|
||||
@ -193,13 +382,13 @@ function radioMenu.doMenuX(args)
|
||||
|
||||
-- set new cooldown -- needs own decoder A..X
|
||||
if theItemIndex == "A" then
|
||||
theZone.mcdA = now + theZone.cooldownA
|
||||
radioMenu.setCDByGID("mcdA", theZone, theGroup, now + theZone.cooldownA)
|
||||
elseif theItemIndex == "B" then
|
||||
theZone.mcdB = now + theZone.cooldownB
|
||||
radioMenu.setCDByGID("mcdB", theZone, theGroup, now + theZone.cooldownB)
|
||||
elseif theItemIndex == "C" then
|
||||
theZone.mcdC = now + theZone.cooldownC
|
||||
radioMenu.setCDByGID("mcdC", theZone, theGroup, now + theZone.cooldownC)
|
||||
else
|
||||
theZone.mcdD = now + theZone.cooldownD
|
||||
radioMenu.setCDByGID("mcdC", theZone, theGroup, now + theZone.cooldownC)
|
||||
end
|
||||
|
||||
cfxZones.pollFlag(theFlag, theZone.radioMethod, theZone)
|
||||
@ -208,10 +397,10 @@ function radioMenu.doMenuX(args)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--
|
||||
-- Update -- required when we can enable/disable a zone's menu
|
||||
--
|
||||
|
||||
function radioMenu.update()
|
||||
-- call me in a second to poll triggers
|
||||
timer.scheduleFunction(radioMenu.update, {}, timer.getTime() + 1/radioMenu.ups)
|
||||
@ -221,15 +410,15 @@ function radioMenu.update()
|
||||
if theZone.removeMenu
|
||||
and cfxZones.testZoneFlag(theZone, theZone.removeMenu, theZone.radioTriggerMethod, "lastRemoveMenu")
|
||||
and theZone.menuVisible
|
||||
then
|
||||
if theZone.verbose or radioMenu.verbose then
|
||||
trigger.action.outText("+++menu: removing <" .. dcsCommon.menu2text(theZone.rootMenu) .. "> for <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
|
||||
if theZone.coalition == 0 then
|
||||
missionCommands.removeItem(theZone.rootMenu)
|
||||
then
|
||||
if theZone.menuGroup or theZone.menuTypes then
|
||||
for gID, aRoot in pairs(theZone.rootMenu) do
|
||||
missionCommands.removeItemForGroup(gID, aRoot)
|
||||
end
|
||||
elseif theZone.coalition == 0 then
|
||||
missionCommands.removeItem(theZone.rootMenu[0])
|
||||
else
|
||||
missionCommands.removeItemForCoalition(theZone.coalition, theZone.rootMenu)
|
||||
missionCommands.removeItemForCoalition(theZone.coalition, theZone.rootMenu[0])
|
||||
end
|
||||
|
||||
theZone.menuVisible = false
|
||||
@ -304,5 +493,6 @@ if not radioMenu.start() then
|
||||
end
|
||||
|
||||
--[[--
|
||||
callbacks for the menus
|
||||
callbacks for the menus
|
||||
check CD/standby code for multiple groups
|
||||
--]]--
|
||||
440
modules/sequencer.lua
Normal file
440
modules/sequencer.lua
Normal file
@ -0,0 +1,440 @@
|
||||
sequencer = {}
|
||||
sequencer.version = "1.0.0"
|
||||
sequencer.verbose = false
|
||||
sequencer.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
}
|
||||
--[[--
|
||||
Sequencer: pull flags in a sequence with oodles of features
|
||||
|
||||
Copyright (c) 2022 by Christian Franz
|
||||
--]]--
|
||||
|
||||
sequencer.sequencers = {}
|
||||
|
||||
function sequencer.addSequencer(theZone)
|
||||
if not theZone then return end
|
||||
table.insert(sequencer.sequencers, theZone)
|
||||
end
|
||||
|
||||
function sequencer.getSequenceByName(aName)
|
||||
if not aName then return nil end
|
||||
for idx, aZone in pairs(sequencer.sequencers) do
|
||||
if aZone.name == aName then return aZone end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--
|
||||
-- read from ME
|
||||
--
|
||||
|
||||
function sequencer.createSequenceWithZone(theZone)
|
||||
local seqRaw = cfxZones.getStringFromZoneProperty(theZone, "sequence!", "none")
|
||||
local theFlags = dcsCommon.flagArrayFromString(seqRaw)
|
||||
theZone.sequence = theFlags
|
||||
local interRaw = cfxZones.getStringFromZoneProperty(theZone, "intervals", "86400")
|
||||
if cfxZones.hasProperty(theZone, "interval") then
|
||||
interRaw = cfxZones.getStringFromZoneProperty(theZone, "interval", "86400") -- = 24 * 3600 = 24 hours default interval
|
||||
end
|
||||
|
||||
local theIntervals = dcsCommon.rangeArrayFromString(interRaw, false)
|
||||
theZone.intervals = theIntervals
|
||||
|
||||
theZone.seqIndex = 1 -- we start at one
|
||||
theZone.intervalIndex = 1 -- here too
|
||||
|
||||
theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false)
|
||||
theZone.zeroSequence = cfxZones.getBoolFromZoneProperty(theZone, "zeroSequence", true)
|
||||
|
||||
theZone.seqLoop = cfxZones.getBoolFromZoneProperty(theZone, "loop", false)
|
||||
|
||||
theZone.seqRunning = false
|
||||
theZone.seqComplete = false
|
||||
theZone.seqStarted = false
|
||||
|
||||
theZone.timeLimit = 0 -- will be set to when we expire
|
||||
if cfxZones.hasProperty(theZone, "done!") then
|
||||
theZone.seqDone = cfxZones.getStringFromZoneProperty(theZone, "done!", "<none>")
|
||||
elseif cfxZones.hasProperty(theZone, "seqDone!") then
|
||||
theZone.seqDone = cfxZones.getStringFromZoneProperty(theZone, "seqDone!", "<none>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "next?") then
|
||||
theZone.nextSeq = cfxZones.getStringFromZoneProperty(theZone, "next?", "<none>")
|
||||
theZone.lastNextSeq = cfxZones.getFlagValue(theZone.nextSeq, theZone)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "startSeq?") then
|
||||
theZone.startSeq = cfxZones.getStringFromZoneProperty(theZone, "startSeq?", "<none>")
|
||||
theZone.lastStartSeq = cfxZones.getFlagValue(theZone.startSeq, theZone)
|
||||
--trigger.action.outText("read as " .. theZone.startSeq, 30)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "stopSeq?") then
|
||||
theZone.stopSeq = cfxZones.getStringFromZoneProperty(theZone, "stopSeq?", "<none>")
|
||||
theZone.lastStopSeq = cfxZones.getFlagValue(theZone.stopSeq, theZone)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "resetSeq?") then
|
||||
theZone.resetSeq = cfxZones.getStringFromZoneProperty(theZone, "resetSeq?", "<none>")
|
||||
theZone.lastResetSeq = cfxZones.getFlagValue(theZone.resetSeq, theZone)
|
||||
end
|
||||
|
||||
|
||||
-- methods
|
||||
theZone.seqMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
|
||||
if cfxZones.hasProperty(theZone, "seqMethod") then
|
||||
theZone.seqMethod = cfxZones.getStringFromZoneProperty(theZone, "seqMethod", "inc")
|
||||
end
|
||||
|
||||
theZone.seqTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
|
||||
if cfxZones.hasProperty(theZone, "seqTriggerMethod") then
|
||||
theZone.seqTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "seqTriggerMethod", "change")
|
||||
end
|
||||
|
||||
if (not theZone.onStart) and not (theZone.startSeq) then
|
||||
trigger.action.outText("+++seq: WARNING - sequence <" .. theZone.name .. "> cannot be started: no startSeq? and onStart is false", 30)
|
||||
end
|
||||
end
|
||||
|
||||
function sequencer.fire(theZone)
|
||||
-- time's up. poll flag at index
|
||||
local theFlag = theZone.sequence[theZone.seqIndex]
|
||||
if theFlag then
|
||||
cfxZones.pollFlag(theFlag, theZone.seqMethod, theZone)
|
||||
if theZone.verbose or sequencer.verbose then
|
||||
trigger.action.outText("+++seq: triggering flag <" .. theFlag .. "> for index <" .. theZone.seqIndex .. "> in sequence <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
else
|
||||
trigger.action.outText("+++seq: ran out of sequences for <" .. theZone.name .. "> on index <" .. theZone.seqIndex .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
function sequencer.advanceInterval(theZone)
|
||||
theZone.intervalIndex = theZone.intervalIndex + 1
|
||||
if theZone.intervalIndex > #theZone.intervals then
|
||||
theZone.intervalIndex = 1 -- always loops
|
||||
end
|
||||
end
|
||||
|
||||
function sequencer.advanceSeq(theZone)
|
||||
-- get the next index for the sequence
|
||||
theZone.seqIndex = theZone.seqIndex + 1
|
||||
|
||||
-- loop if over and enabled
|
||||
if theZone.seqIndex > #theZone.sequence then
|
||||
if theZone.seqLoop then
|
||||
theZone.seqIndex = 1
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
-- returns true if success
|
||||
return true
|
||||
end
|
||||
|
||||
function sequencer.startWaitCycle(theZone)
|
||||
if theZone.seqComplete then return end
|
||||
local bounds = theZone.intervals[theZone.intervalIndex]
|
||||
local newInterval = dcsCommon.randomBetween(bounds[1], bounds[2])
|
||||
theZone.timeLimit = timer.getTime() + newInterval
|
||||
if theZone.verbose or sequencer.verbose then
|
||||
trigger.action.outText("+++seq: start wait for <" .. newInterval .. "> in sequence <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
function sequencer.pause(theZone)
|
||||
if theZone.seqComplete then return end
|
||||
if not theZone.seqRunning then return end
|
||||
local now = timer.getTime()
|
||||
theZone.timeRemaining = theZone.timeLimit - now
|
||||
theZone.seqRunning = false
|
||||
end
|
||||
|
||||
function sequencer.continue(theZone)
|
||||
if theZone.seqComplete then return end -- Frankie says: no more
|
||||
if theZone.seqRunning then return end -- we are already running
|
||||
|
||||
-- reset any lingering 'next' flags so they don't
|
||||
-- trigger a newly started sequence
|
||||
if theZone.nextSeq then
|
||||
theZone.lastNextSeq = cfxZones.getFlagValue(theZone.nextSeq, theZone)
|
||||
end
|
||||
|
||||
if not theZone.seqStarted then
|
||||
-- this is the very first time we are running.
|
||||
if theZone.zeroSequence then
|
||||
-- start with a bang
|
||||
sequencer.fire(theZone)
|
||||
sequencer.advanceSeq(theZone)
|
||||
end
|
||||
theZone.seqRunning = true
|
||||
theZone.seqStarted = true
|
||||
sequencer.startWaitCycle(theZone)
|
||||
return
|
||||
end
|
||||
|
||||
-- we are continuing a paused sequencer
|
||||
local now = timer.getTime()
|
||||
if not theZone.timeRemaining then theZone.timeRemaining = 1 end
|
||||
theZone.timeLimit = now + theZone.timeRemaining
|
||||
theZone.seqRunning = true
|
||||
end
|
||||
|
||||
function sequencer.reset(theZone)
|
||||
theZone.seqComplete = false
|
||||
theZone.seqRunning = false
|
||||
theZone.seqIndex = 1 -- we start at one
|
||||
theZone.intervalIndex = 1 -- here too
|
||||
theZone.seqStarted = false
|
||||
if theZone.onStart then
|
||||
theZone.continue(theZone)
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
--- update
|
||||
---
|
||||
function sequencer.update()
|
||||
-- call me in a second to poll triggers
|
||||
local now = timer.getTime()
|
||||
timer.scheduleFunction(sequencer.update, {}, now + 1)
|
||||
|
||||
for idx, theZone in pairs(sequencer.sequencers) do
|
||||
-- see if reset was pulled
|
||||
if theZone.resetSeq and cfxZones.testZoneFlag(theZone, theZone.resetSeq, theZone.seqTriggerMethod, "lastResetSeq") then
|
||||
sequencer.reset(theZone)
|
||||
end
|
||||
|
||||
--trigger.action.outText("have as " .. theZone.startSeq, 30)
|
||||
-- first, check if we need to pause or continue
|
||||
if (not theZone.seqRunning) and theZone.startSeq and
|
||||
cfxZones.testZoneFlag(theZone, theZone.startSeq, theZone.seqTriggerMethod, "lastStartSeq") then
|
||||
sequencer.continue(theZone)
|
||||
if theZone.verbose or sequencer.verbose then
|
||||
trigger.action.outText("+++seq: continuing sequencer <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
else
|
||||
-- synch the start flag so we don't immediately trigger
|
||||
-- when it starts
|
||||
if theZone.startSeq then
|
||||
theZone.lastStartSeq = cfxZones.getFlagValue(theZone.startSeq, theZone)
|
||||
end
|
||||
end
|
||||
|
||||
if theZone.seqRunning and theZone.stopSeq and
|
||||
cfxZones.testZoneFlag(theZone, theZone.stopSeq, theZone.seqTriggerMethod, "lastStopSeq") then
|
||||
sequencer.pause(theZone)
|
||||
if theZone.verbose or sequencer.verbose then
|
||||
trigger.action.outText("+++seq: pausing sequencer <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
else
|
||||
if theZone.stopSeq then
|
||||
theZone.lastStopSeq = cfxZones.getFlagValue(theZone.stopSeq, theZone)
|
||||
end
|
||||
end
|
||||
|
||||
-- if we are running, see if we timed out
|
||||
if theZone.seqRunning then
|
||||
-- check if we have received a 'next' signal
|
||||
local doNext = false
|
||||
if theZone.nextSeq then
|
||||
doNext = cfxZones.testZoneFlag(theZone, theZone.nextSeq, theZone.seqTriggerMethod, "lastNextSeq")
|
||||
if doNext and (sequencer.verbose or theZone.verbose) then
|
||||
trigger.action.outText("+++seq: 'next' command received for sequencer <" .. theZone.name .. "> on <" .. theZone.nextSeq .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
-- check if we are over time limit
|
||||
if doNext or (theZone.timeLimit < now) then
|
||||
-- we are timed out or triggered!
|
||||
if theZone.nextSeq then
|
||||
theZone.lastNextSeq = cfxZones.getFlagValue(theZone.nextSeq, theZone)
|
||||
end
|
||||
sequencer.fire(theZone)
|
||||
sequencer.advanceInterval(theZone)
|
||||
if sequencer.advanceSeq(theZone) then
|
||||
-- start next round
|
||||
sequencer.startWaitCycle(theZone)
|
||||
else
|
||||
if theZone.seqDone then
|
||||
cfxZones.pollFlag(theZone.seqDone, theZone.seqMethod, theZone)
|
||||
if theZone.verbose or sequencer.verbose then
|
||||
trigger.action.outText("+++seq: banging done! flag <" .. theZone.seqDone .. "> for sequence <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
end
|
||||
theZone.seqRunning = false
|
||||
theZone.seqComplete = true -- can't be restarted unless reset
|
||||
end -- else no advance
|
||||
end -- if time limit
|
||||
end -- if running
|
||||
end -- for all sequencers
|
||||
end
|
||||
|
||||
--
|
||||
-- start cycle: force all onStart to fire
|
||||
--
|
||||
function sequencer.startCycle()
|
||||
for idx, theZone in pairs(sequencer.sequencers) do
|
||||
-- a sequence can be already running when persistence
|
||||
-- loaded a sequencer
|
||||
if theZone.onStart then
|
||||
if theZone.seqStarted then
|
||||
-- suppressed by persistence
|
||||
else
|
||||
if sequencer.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++seq: starting sequencer " .. theZone.name, 30)
|
||||
end
|
||||
sequencer.continue(theZone)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- LOAD / SAVE
|
||||
--
|
||||
function sequencer.saveData()
|
||||
local theData = {}
|
||||
local allSequencers = {}
|
||||
local now = timer.getTime()
|
||||
for idx, theSeq in pairs(sequencer.sequencers) do
|
||||
local theName = theSeq.name
|
||||
local seqData = {}
|
||||
seqData.seqComplete = theSeq.seqComplete
|
||||
seqData.seqRunning = theSeq.seqRunning
|
||||
seqData.seqIndex = theSeq.seqIndex
|
||||
seqData.intervalIndex = theSeq.intervalIndex
|
||||
seqData.seqStarted = theSeq.seqStarted
|
||||
seqData.timeRemaining = theSeq.timeRemaining
|
||||
if theSeq.seqRunning then
|
||||
seqData.timeRemaining = theSeq.timeLimit - now
|
||||
end
|
||||
|
||||
allSequencers[theName] = seqData
|
||||
end
|
||||
theData.allSequencers = allSequencers
|
||||
return theData
|
||||
end
|
||||
|
||||
function sequencer.loadData()
|
||||
if not persistence then return end
|
||||
local theData = persistence.getSavedDataForModule("sequencer")
|
||||
if not theData then
|
||||
if sequencer.verbose then
|
||||
trigger.action.outText("+++seq Persistence: no save date received, skipping.", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local allSequencers = theData.allSequencers
|
||||
if not allSequencers then
|
||||
if sequencer.verbose then
|
||||
trigger.action.outText("+++seq Persistence: no sequencer data, skipping", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local now = timer.getTime()
|
||||
for theName, seqData in pairs(allSequencers) do
|
||||
local theSeq = sequencer.getSequenceByName(theName)
|
||||
if theSeq then
|
||||
theSeq.seqComplete = seqData.seqComplete
|
||||
theSeq.seqIndex = seqData.seqIndex
|
||||
theSeq.intervalIndex = seqData.intervalIndex
|
||||
theSeq.seqStarted = seqData.seqStarted
|
||||
theSeq.seqRunning = seqData.seqRunning
|
||||
theSeq.timeRemaining = seqData.timeRemaining
|
||||
if theSeq.seqRunning then
|
||||
theSeq.timeLimit = now + theSeq.timeRemaining
|
||||
end
|
||||
|
||||
else
|
||||
trigger.action.outText("+++seq: persistence: cannot synch sequencer <" .. theName .. ">, skipping", 40)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- start module and read config
|
||||
--
|
||||
function sequencer.readConfigZone()
|
||||
-- note: must match exactly!!!!
|
||||
local theZone = cfxZones.getZoneByName("sequencerConfig")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone("sequencerConfig")
|
||||
if sequencer.verbose then
|
||||
trigger.action.outText("***RND: NO config zone!", 30)
|
||||
end
|
||||
end
|
||||
|
||||
sequencer.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
|
||||
if sequencer.verbose then
|
||||
trigger.action.outText("***RND: read config", 30)
|
||||
end
|
||||
end
|
||||
|
||||
function sequencer.start()
|
||||
-- lib check
|
||||
if not dcsCommon then
|
||||
trigger.action.outText("sequencer requires dcsCommon", 30)
|
||||
return false
|
||||
end
|
||||
if not dcsCommon.libCheck("cfx Sequencer",
|
||||
sequencer.requiredLibs) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- read config
|
||||
sequencer.readConfigZone()
|
||||
|
||||
-- process RND Zones
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("sequence!")
|
||||
|
||||
if sequencer.verbose then
|
||||
local a = dcsCommon.getSizeOfTable(attrZones)
|
||||
trigger.action.outText("sequencers: " .. a, 30)
|
||||
end
|
||||
|
||||
-- now create an rnd gen for each one and add them
|
||||
-- to our watchlist
|
||||
for k, aZone in pairs(attrZones) do
|
||||
sequencer.createSequenceWithZone(aZone) -- process attribute and add to zone
|
||||
sequencer.addSequencer(aZone) -- remember it so we can smoke it
|
||||
end
|
||||
|
||||
|
||||
-- persistence
|
||||
if persistence then
|
||||
-- sign up for persistence
|
||||
callbacks = {}
|
||||
callbacks.persistData = sequencer.saveData
|
||||
persistence.registerModule("sequencer", callbacks)
|
||||
-- now load my data
|
||||
sequencer.loadData()
|
||||
end
|
||||
|
||||
-- schedule start cycle
|
||||
timer.scheduleFunction(sequencer.startCycle, {}, timer.getTime() + 0.25)
|
||||
|
||||
-- start update
|
||||
timer.scheduleFunction(sequencer.update, {}, timer.getTime() + 1)
|
||||
|
||||
trigger.action.outText("cfx Sequencer v" .. sequencer.version .. " started.", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
-- let's go!
|
||||
if not sequencer.start() then
|
||||
trigger.action.outText("cf/x Sequencer aborted: missing libraries", 30)
|
||||
sequencer = nil
|
||||
end
|
||||
|
||||
--[[--
|
||||
to do:
|
||||
- currSeq always returns current sequence number
|
||||
- timeLeft returns current time limit in seconds
|
||||
--]]--
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user