Version 2.4.4

Removed show-stopping bug in removeMark(), now stops smoke, better drones.
This commit is contained in:
Christian Franz 2025-02-26 19:29:19 +01:00
parent dff5faa06e
commit 2f80033077
18 changed files with 26578 additions and 26593 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,5 +1,5 @@
FARPZones = {} FARPZones = {}
FARPZones.version = "2.3.0" FARPZones.version = "2.4.0"
FARPZones.verbose = false FARPZones.verbose = false
--[[-- --[[--
Version History Version History
@ -28,6 +28,7 @@ FARPZones.verbose = false
2.2.0 - changing a FARP's owner invokes SSBClient if it is loaded 2.2.0 - changing a FARP's owner invokes SSBClient if it is loaded
2.3.0 - new attributes redCap!, blueCap! captured! and farpMethod 2.3.0 - new attributes redCap!, blueCap! captured! and farpMethod
- send out signals - send out signals
2.4.0 - work-around for crashing DCS bug in trigger.action.removeMark
--]]-- --]]--
@ -564,6 +565,7 @@ function FARPZones.loadMission()
local farps = theData.farps local farps = theData.farps
if farps then if farps then
local delay = timer.getTime() + 0.2
for fName, fData in pairs(farps) do for fName, fData in pairs(farps) do
local theFARP = FARPZones.getFARPZoneByName(fName) local theFARP = FARPZones.getFARPZoneByName(fName)
if theFARP then if theFARP then
@ -574,8 +576,10 @@ function FARPZones.loadMission()
theFARP.defenderData = dcsCommon.clone(fData.defenderData) theFARP.defenderData = dcsCommon.clone(fData.defenderData)
FARPZones.produceVehicles(theFARP) -- do full defender and resource cycle FARPZones.produceVehicles(theFARP) -- do full defender and resource cycle
FARPZones.drawFARPCircleInMap(theFARP) -- mark in map -- stagger drawing the map in time to
-- prevent removeMark crashing the thread
timer.scheduleFunction(FARPZones.drawFARPCircleInMap, theFARP, delay) -- mark in map
delay = delay + 0.2
else else
trigger.action.outText("frpZ: persistence: FARP <" .. fName .. "> no longer exists in mission, skipping", 30) trigger.action.outText("frpZ: persistence: FARP <" .. fName .. "> no longer exists in mission, skipping", 30)
end end

View File

@ -9,7 +9,7 @@ rndFlags.requiredLibs = {
Random Flags: DML module to select flags at random Random Flags: DML module to select flags at random
and then change them and then change them
Copyright 2022 by Christian Franz and cf/x Copyright 2022-2025 by Christian Franz and cf/x
Version History Version History

View File

@ -1,5 +1,5 @@
cfxZones = {} cfxZones = {}
cfxZones.version = "4.5.2" cfxZones.version = "4.5.3"
-- cf/x zone management module -- cf/x zone management module
-- reads dcs zones and makes them accessible and mutable -- reads dcs zones and makes them accessible and mutable
@ -9,39 +9,15 @@ cfxZones.version = "4.5.2"
-- --
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
- 4.1.0 - getBoolFromZoneProperty 'on/off' support for dml variant as well
- 4.1.1 - evalRemainder() updates
- 4.1.2 - hash property missing warning
- 4.2.0 - new createRandomPointInPopulatedZone()
- 4.3.0 - boolean supports maybe, random, rnd, ?
- small optimization for randomInRange()
- randomDelayFromPositiveRange also allows 0
- 4.3.1 - new drawText() for zones
- dmlZone:getClosestZone() bridge
- 4.3.2 - new getListFromZoneProperty()
- 4.3.3 - hardened calculateZoneBounds
- 4.3.4 - rewrote zone bounds for poly zones
- 4.3.5 - hardened getStringFromZoneProperty against number value returns (WebEd bug)
- 4.3.6 - tiny optimization in isPointInsideQuad
- moving zone - hardening code for static objects
- moving zones - now deriving dx, dy,uHeading from dcsCommon xref for linked zones
- 4.3.7 - corrected bug in processDynamicValues for lookup table
- 4.4.0 - dmlZone:getCoalition()
- dmlZone:getTypeName()
- dmlZone supports masterOwner by default
- dmlZone:getCoalition() dereferences masterOwner once
-4.4.1 - better verbosity for error in doPollFlag()
-4.4.2 - twn support for wildcards <twn: > and <loc:>
-4.4.3 - property name is trimmed (double check)
-4.4.4 - createGroundUnitsInZoneForCoalition supports drivable
-4.4.5 - corrected startMovingZones() for linked zones via ME's LINKZONE drop-down
-4.4.6 - corrected pattern bug in processDynamicAB()
-4.5.0 - corrected bug in getBoolFromZoneProperty for default = false and "rnd" -4.5.0 - corrected bug in getBoolFromZoneProperty for default = false and "rnd"
- rnd in bool can have = xxx param for percentage - rnd in bool can have = xxx param for percentage
- getSmokeColorNumberFromZoneProperty() - getSmokeColorNumberFromZoneProperty()
-4.5.1 - moved processSimpleZoneDynamics to common -4.5.1 - moved processSimpleZoneDynamics to common
-4.5.2 - NEW getAllZoneProperties() -4.5.2 - NEW getAllZoneProperties()
- guard agains DCS radius stored as sting (WTF, ED?) - guard agains DCS radius stored as sting (WTF, ED?)
-4.5.3 - added name for all smoke meths
- getSmokeColorStringFromZoneProperty() supports "?"
- getSmokeColorNumberFromZoneProperty() supports "?"
--]]-- --]]--
-- --
@ -71,7 +47,6 @@ end
-- dmlZone:getTypeName() -- returns "dmlZone" -- dmlZone:getTypeName() -- returns "dmlZone"
-- dmlZone:getCoalition -- returns owner -- dmlZone:getCoalition -- returns owner
-- --
-- CLASSIC INTERFACE -- CLASSIC INTERFACE
-- --
@ -92,16 +67,14 @@ function cfxZones.readFromDCS(clearfirst)
trigger.action.outText("cf/x zones: no env.triggers defined", 10) trigger.action.outText("cf/x zones: no env.triggers defined", 10)
end end
return return
end end
if not env.mission.triggers.zones then if not env.mission.triggers.zones then
if cfxZones.verbose then if cfxZones.verbose then
trigger.action.outText("cf/x zones: no zones defined", 10) trigger.action.outText("cf/x zones: no zones defined", 10)
end end
return; return;
end end
-- we only retrieve the data that we need. At this point it is name, location and radius
-- we only retrieve the data we need. At this point it is name, location and radius
-- and put this in our own little structure. we also convert to all upper case name for index -- and put this in our own little structure. we also convert to all upper case name for index
-- and assume that the name may also carry meaning, e.g. 'LZ:' defines a landing zone -- and assume that the name may also carry meaning, e.g. 'LZ:' defines a landing zone
-- so we can quickly create other sets from this -- so we can quickly create other sets from this
@ -133,16 +106,8 @@ function cfxZones.readFromDCS(clearfirst)
else else
newZone.properties = {} newZone.properties = {}
end -- WARNING: REF COPY. May need to clone end -- WARNING: REF COPY. May need to clone
--[[--
trigger.action.outText("zone <> properties:trimmed", 30)
local msg = "["
for idx, val in pairs(newZone.properties) do
msg = msg .. "<" .. val.key .. ">:<" .. dcsCommon.trim(val.key) .. ">, "
end
trigger.action.outText(msg, 30)
--]]--
local upperName = newZone.name:upper()
local upperName = newZone.name:upper()
-- location as 'point' -- location as 'point'
-- WARNING: zones locs are 2D (x,y) pairs, while y in DCS is altitude. -- WARNING: zones locs are 2D (x,y) pairs, while y in DCS is altitude.
-- so we need to change (x,y) into (x, 0, z). Since Zones have no -- so we need to change (x,y) into (x, 0, z). Since Zones have no
@ -160,20 +125,17 @@ function cfxZones.readFromDCS(clearfirst)
newZone.point = dcsCommon.createPoint(dcsZone.x, 0, dcsZone.y) newZone.point = dcsCommon.createPoint(dcsZone.x, 0, dcsZone.y)
newZone.dcsOrigin = dcsCommon.createPoint(dcsZone.x, 0, dcsZone.y) newZone.dcsOrigin = dcsCommon.createPoint(dcsZone.x, 0, dcsZone.y)
end end
-- start type processing. if zone.type exists, we have a mission -- start type processing. if zone.type exists, we have a mission
-- created with 2.7 or above, else earlier -- created with 2.7 or above, else earlier
local zoneType = 0 local zoneType = 0
if (dcsZone.type) then if (dcsZone.type) then
zoneType = dcsZone.type zoneType = dcsZone.type
end end
if zoneType == 0 then if zoneType == 0 then
-- circular zone -- circular zone
newZone.isCircle = true newZone.isCircle = true
newZone.radius = tonumber(dcsZone.radius) newZone.radius = tonumber(dcsZone.radius)
newZone.maxRadius = newZone.radius -- same for circular newZone.maxRadius = newZone.radius -- same for circular
elseif zoneType == 2 then elseif zoneType == 2 then
-- polyZone -- polyZone
newZone.isPoly = true newZone.isPoly = true
@ -190,7 +152,6 @@ function cfxZones.readFromDCS(clearfirst)
-- in later versions, this was corrected -- in later versions, this was corrected
verts = dcsZone.vertices -- see if this is ever called verts = dcsZone.vertices -- see if this is ever called
end end
for v=1, #verts do for v=1, #verts do
local dcsPoint = verts[v] local dcsPoint = verts[v]
local polyPoint = cfxZones.createPointFromDCSPoint(dcsPoint) -- (x, y) --> (x, 0, y-->z) local polyPoint = cfxZones.createPointFromDCSPoint(dcsPoint) -- (x, y) --> (x, 0, y-->z)
@ -201,32 +162,22 @@ function cfxZones.readFromDCS(clearfirst)
if dist > newZone.maxRadius then newZone.maxRadius = dist end if dist > newZone.maxRadius then newZone.maxRadius = dist end
end end
else else
trigger.action.outText("cf/x zones: malformed zone #" .. i .. " unknown type " .. zoneType, 10) trigger.action.outText("cf/x zones: malformed zone #" .. i .. " unknown type " .. zoneType, 10)
end end
-- calculate bounds -- calculate bounds
cfxZones.calculateZoneBounds(newZone) cfxZones.calculateZoneBounds(newZone)
-- add to my table -- add to my table
cfxZones.zones[upperName] = newZone -- WARNING: UPPER ZONE!!! cfxZones.zones[upperName] = newZone -- WARNING: UPPER ZONE!!!
else else
if cfxZones.verbose then if cfxZones.verbose then trigger.action.outText("cf/x zones: malformed zone #" .. i .. " dropped", 10) end
trigger.action.outText("cf/x zones: malformed zone #" .. i .. " dropped", 10)
end
end -- else var not a table end -- else var not a table
end -- for all zones kvp end -- for all zones kvp
end -- readFromDCS end -- readFromDCS
function cfxZones.calculateZoneBounds(theZone) function cfxZones.calculateZoneBounds(theZone)
if not (theZone) then return if not (theZone) then return
end end
local bounds = theZone.bounds -- copy ref! -- DON'T BELIEVE THIS! local bounds = theZone.bounds -- copy ref! -- DON'T BELIEVE THIS!
if theZone.isCircle then if theZone.isCircle then
-- aabb are easy: center +/- radius -- aabb are easy: center +/- radius
local center = theZone.point local center = theZone.point
@ -237,7 +188,6 @@ function cfxZones.calculateZoneBounds(theZone)
bounds.ur = dcsCommon.createPoint(center.x + radius, 0, center.z - radius) bounds.ur = dcsCommon.createPoint(center.x + radius, 0, center.z - radius)
bounds.ll = dcsCommon.createPoint(center.x - radius, 0, center.z + radius) bounds.ll = dcsCommon.createPoint(center.x - radius, 0, center.z + radius)
bounds.lr = dcsCommon.createPoint(center.x + radius, 0, center.z + radius) bounds.lr = dcsCommon.createPoint(center.x + radius, 0, center.z + radius)
-- write back -- write back
theZone.bounds = bounds theZone.bounds = bounds
elseif theZone.isPoly then elseif theZone.isPoly then
@ -245,7 +195,6 @@ function cfxZones.calculateZoneBounds(theZone)
-- create the four points -- create the four points
local p = cfxZones.createPointFromPoint(poly[1]) local p = cfxZones.createPointFromPoint(poly[1])
local pRad = dcsCommon.dist(theZone.point, poly[1]) -- rRad is radius for polygon from theZone.point local pRad = dcsCommon.dist(theZone.point, poly[1]) -- rRad is radius for polygon from theZone.point
-- now iterate through all points and adjust bounds accordingly -- now iterate through all points and adjust bounds accordingly
local lx, ly, mx, my = p.x, p.z, p.x, p.z local lx, ly, mx, my = p.x, p.z, p.x, p.z
for vtx=1, #poly do for vtx=1, #poly do
@ -257,7 +206,6 @@ function cfxZones.calculateZoneBounds(theZone)
local dp = dcsCommon.dist(theZone.point, v) local dp = dcsCommon.dist(theZone.point, v)
if dp > pRad then pRad = dp end -- find largst distance to vertex if dp > pRad then pRad = dp end -- find largst distance to vertex
end end
theZone.bounds.ul = dcsCommon.createPoint(lx, 0, my) theZone.bounds.ul = dcsCommon.createPoint(lx, 0, my)
theZone.bounds.ur = dcsCommon.createPoint(mx, 0, my) theZone.bounds.ur = dcsCommon.createPoint(mx, 0, my)
theZone.bounds.ll = dcsCommon.createPoint(lx, 0, ly) theZone.bounds.ll = dcsCommon.createPoint(lx, 0, ly)
@ -270,7 +218,6 @@ function cfxZones.calculateZoneBounds(theZone)
trigger.action.outText("cf/x zones: calc bounds: zone " .. theZone.name .. " has unknown type", 30) trigger.action.outText("cf/x zones: calc bounds: zone " .. theZone.name .. " has unknown type", 30)
end end
end end
end end
function dmlZone:calculateZoneBounds() function dmlZone:calculateZoneBounds()
@ -308,7 +255,6 @@ function cfxZones.createPointFromDCSPoint(inPoint)
return dcsCommon.createPoint(inPoint.x, 0, inPoint.y) return dcsCommon.createPoint(inPoint.x, 0, inPoint.y)
end end
function cfxZones.createRandomPointInsideBounds(bounds) function cfxZones.createRandomPointInsideBounds(bounds)
-- warning: bounds do not move woth zone! may have to be updated -- warning: bounds do not move woth zone! may have to be updated
local x = math.random(bounds.ll.x, ur.x) local x = math.random(bounds.ll.x, ur.x)
@ -362,8 +308,7 @@ function cfxZones.createRandomPointInCircleZone(theZone, onEdge)
if not theZone.isCircle then if not theZone.isCircle then
trigger.action.outText("+++Zones: warning - createRandomPointInCircleZone called for non-circle zone <" .. theZone.name .. ">", 30) trigger.action.outText("+++Zones: warning - createRandomPointInCircleZone called for non-circle zone <" .. theZone.name .. ">", 30)
return {x=theZone.point.x, y=0, z=theZone.point.z} return {x=theZone.point.x, y=0, z=theZone.point.z}
end end
-- ok, let's first create a random percentage value for the new radius -- ok, let's first create a random percentage value for the new radius
-- now lets get a random degree -- now lets get a random degree
local degrees = math.random() * 2 * 3.14152 -- radiants. local degrees = math.random() * 2 * 3.14152 -- radiants.
@ -391,11 +336,9 @@ function cfxZones.createRandomPointInPolyZone(theZone, onEdge)
end end
-- force update of all points -- force update of all points
local p = cfxZones.getPoint(theZone) local p = cfxZones.getPoint(theZone)
-- point in convex poly: choose two different lines from that polygon -- point in convex poly: choose two different lines from that polygon
local lineIdxA = dcsCommon.smallRandom(#theZone.poly) local lineIdxA = dcsCommon.smallRandom(#theZone.poly)
repeat lineIdxB = dcsCommon.smallRandom(#theZone.poly) until (lineIdxA ~= lineIdxB) repeat lineIdxB = dcsCommon.smallRandom(#theZone.poly) until (lineIdxA ~= lineIdxB)
-- we now have two different lines. pick a random point on each. -- we now have two different lines. pick a random point on each.
-- we use lerp to pick any point between a and b -- we use lerp to pick any point between a and b
local a = theZone.poly[lineIdxA] local a = theZone.poly[lineIdxA]
@ -409,7 +352,6 @@ function cfxZones.createRandomPointInPolyZone(theZone, onEdge)
local polyPoint = sourceA local polyPoint = sourceA
return polyPoint, polyPoint.x - p.x, polyPoint.z - p.z -- return loc, dx, dz return polyPoint, polyPoint.x - p.x, polyPoint.z - p.z -- return loc, dx, dz
end end
-- now get point on second line -- now get point on second line
a = theZone.poly[lineIdxB] a = theZone.poly[lineIdxB]
lineIdxB = lineIdxB + 1 -- get next point in poly and wrap around lineIdxB = lineIdxB + 1 -- get next point in poly and wrap around
@ -417,7 +359,6 @@ function cfxZones.createRandomPointInPolyZone(theZone, onEdge)
b = theZone.poly[lineIdxB] b = theZone.poly[lineIdxB]
randompercent = math.random() randompercent = math.random()
local sourceB = dcsCommon.vLerp (a, b, randompercent) local sourceB = dcsCommon.vLerp (a, b, randompercent)
-- now take a random point on that line that entirely -- now take a random point on that line that entirely
-- runs through the poly -- runs through the poly
randompercent = math.random() randompercent = math.random()
@ -443,14 +384,9 @@ function dmlZone:createRandomPointInPopulatedZone(radius, maxTries)
local o = collector[1] local o = collector[1]
local op = o:getPoint() local op = o:getPoint()
d = dcsCommon.distFlat(op, p) d = dcsCommon.distFlat(op, p)
-- trigger.action.outText("singleDist = " .. d, 30) if d > radius/2 then return p, dx, dz end
if d > radius/2 then
-- trigger.action.outText("good enough, will use", 30)
return p, dx, dz
end
end end
cnt = cnt + 1 cnt = cnt + 1
-- trigger.action.outText(hits .. "hits --> failed try " .. cnt, 30)
until cnt > maxTries until cnt > maxTries
return p, dx, dz return p, dx, dz
end end
@ -489,15 +425,9 @@ function cfxZones.objectsInRange(pt, range)
local op = anObject:getPoint() local op = anObject:getPoint()
local dist = dcsCommon.dist(pt, op) local dist = dcsCommon.dist(pt, op)
if dist < range then if dist < range then
-- local e = {
-- dist = dist,
-- o = anObject
-- }
-- table.insert(filtered, e)
table.insert(filtered, anObject) table.insert(filtered, anObject)
end end
end end
return #filtered, filtered return #filtered, filtered
end end
@ -544,18 +474,14 @@ function cfxZones.createCircleZone(name, x, z, radius)
newZone.isPoly = false newZone.isPoly = false
newZone.poly = {} newZone.poly = {}
newZone.bounds = {} newZone.bounds = {}
newZone.name = name newZone.name = name
newZone.radius = radius newZone.radius = radius
newZone.point = dcsCommon.createPoint(x, 0, z) newZone.point = dcsCommon.createPoint(x, 0, z)
newZone.dcsOrigin = dcsCommon.createPoint(x, 0, z) newZone.dcsOrigin = dcsCommon.createPoint(x, 0, z)
-- props -- props
newZone.properties = {} newZone.properties = {}
-- calculate my bounds -- calculate my bounds
cfxZones.calculateZoneBounds(newZone) cfxZones.calculateZoneBounds(newZone)
return newZone return newZone
end end
@ -567,9 +493,7 @@ function cfxZones.createSimplePolyZone(name, location, points, addToManaged)
if not location.x then location.x = 0 end if not location.x then location.x = 0 end
if not location.z then location.z = 0 end if not location.z then location.z = 0 end
if not location.y then location.y = 0 end if not location.y then location.y = 0 end
local newZone = cfxZones.createPolyZone(name, points, location) local newZone = cfxZones.createPolyZone(name, points, location)
if addToManaged then if addToManaged then
cfxZones.addZoneToManagedZones(newZone) cfxZones.addZoneToManagedZones(newZone)
end end
@ -617,7 +541,6 @@ function cfxZones.createPolyZone(name, poly, location) -- poly must be array of
newZone.isPoly = true newZone.isPoly = true
newZone.poly = {} newZone.poly = {}
newZone.bounds = {} newZone.bounds = {}
newZone.name = name newZone.name = name
newZone.radius = 0 newZone.radius = 0
-- copy poly -- copy poly
@ -625,10 +548,8 @@ function cfxZones.createPolyZone(name, poly, location) -- poly must be array of
local theVertex = poly[v] local theVertex = poly[v]
newZone.poly[v] = cfxZones.createPointFromPoint(theVertex) newZone.poly[v] = cfxZones.createPointFromPoint(theVertex)
end end
-- properties -- properties
newZone.properties = {} newZone.properties = {}
cfxZones.calculateZoneBounds(newZone) cfxZones.calculateZoneBounds(newZone)
return newZone return newZone
end end
@ -638,7 +559,6 @@ function cfxZones.createRandomZoneInZone(name, inZone, targetRadius, entirelyIns
-- if entirelyInside is false, only the zone's center is guaranteed to be inside -- if entirelyInside is false, only the zone's center is guaranteed to be inside
-- inZone. -- inZone.
-- entirelyInside is not guaranteed for polyzones -- entirelyInside is not guaranteed for polyzones
if inZone.isCircle then if inZone.isCircle then
local sourceRadius = inZone.radius local sourceRadius = inZone.radius
if entirelyInside and targetRadius > sourceRadius then targetRadius = sourceRadius end if entirelyInside and targetRadius > sourceRadius then targetRadius = sourceRadius end
@ -654,13 +574,11 @@ function cfxZones.createRandomZoneInZone(name, inZone, targetRadius, entirelyIns
-- construct new zone -- construct new zone
local newZone = cfxZones.createCircleZone(name, x, z, targetRadius) local newZone = cfxZones.createCircleZone(name, x, z, targetRadius)
return newZone return newZone
elseif inZone.isPoly then elseif inZone.isPoly then
local newPoint = cfxZones.createRandomPointInPolyZone(inZone) local newPoint = cfxZones.createRandomPointInPolyZone(inZone)
-- construct new zone -- construct new zone
local newZone = cfxZones.createCircleZone(name, newPoint.x, newPoint.z, targetRadius) local newZone = cfxZones.createCircleZone(name, newPoint.x, newPoint.z, targetRadius)
return newZone return newZone
else else
-- zone type unknown -- zone type unknown
trigger.action.outText("CreateZoneInZone: unknown zone type for inZone =" .. inZone.name , 10) trigger.action.outText("CreateZoneInZone: unknown zone type for inZone =" .. inZone.name , 10)
@ -707,7 +625,6 @@ function cfxZones.isPointInsidePoly(thePoint, poly)
end end
-- final test -- final test
if cfxZones.isLeftXZ(poly[#poly], poly[1], thePoint) ~= mustMatch then return false end if cfxZones.isLeftXZ(poly[#poly], poly[1], thePoint) ~= mustMatch then return false end
return true return true
end; end;
@ -720,12 +637,10 @@ function cfxZones.isPointInsideZone(thePoint, theZone, radiusIncrease)
local d = dcsCommon.dist(p, theZone.point) local d = dcsCommon.dist(p, theZone.point)
return d < theZone.radius + radiusIncrease, d return d < theZone.radius + radiusIncrease, d
end end
if (theZone.isPoly) then if (theZone.isPoly) then
--trigger.action.outText("zne: isPointInside: " .. theZone.name .. " is Polyzone!", 30) --trigger.action.outText("zne: isPointInside: " .. theZone.name .. " is Polyzone!", 30)
return (cfxZones.isPointInsidePoly(p, theZone.poly)), 0 -- always returns delta 0 return (cfxZones.isPointInsidePoly(p, theZone.poly)), 0 -- always returns delta 0
end end
trigger.action.outText("isPointInsideZone: Unknown zone type for " .. outerZone.name, 10) trigger.action.outText("isPointInsideZone: Unknown zone type for " .. outerZone.name, 10)
end end
@ -747,14 +662,12 @@ function cfxZones.getZonesContainingPoint(thePoint, testZones) -- return array
if not testZones then if not testZones then
testZones = cfxZones.zones testZones = cfxZones.zones
end end
local containerZones = {} local containerZones = {}
for tName, tData in pairs(testZones) do for tName, tData in pairs(testZones) do
if cfxZones.isPointInsideZone(thePoint, tData) then if cfxZones.isPointInsideZone(thePoint, tData) then
table.insert(containerZones, tData) table.insert(containerZones, tData)
end end
end end
return containerZones return containerZones
end end
@ -762,13 +675,11 @@ function cfxZones.getFirstZoneContainingPoint(thePoint, testZones)
if not testZones then if not testZones then
testZones = cfxZones.zones testZones = cfxZones.zones
end end
for tName, tData in pairs(testZones) do for tName, tData in pairs(testZones) do
if cfxZones.isPointInsideZone(thePoint, tData) then if cfxZones.isPointInsideZone(thePoint, tData) then
return tData return tData
end end
end end
return nil return nil
end end
@ -776,7 +687,6 @@ function cfxZones.getAllZonesInsideZone(superZone, testZones) -- returnes array!
if not testZones then if not testZones then
testZones = cfxZones.zones testZones = cfxZones.zones
end end
local containedZones = {} local containedZones = {}
for zName, zData in pairs(testZones) do for zName, zData in pairs(testZones) do
if cfxZones.isZoneInsideZone(zData, superZone) then if cfxZones.isZoneInsideZone(zData, superZone) then
@ -794,10 +704,8 @@ function dmlZone:getAllZonesInsideZone(testZones)
return cfxZones.getAllZonesInsideZone(self, testZones) return cfxZones.getAllZonesInsideZone(self, testZones)
end end
function cfxZones.getZonesWithAttributeNamed(attributeName, testZones) function cfxZones.getZonesWithAttributeNamed(attributeName, testZones)
if not testZones then testZones = cfxZones.zones end if not testZones then testZones = cfxZones.zones end
local attributZones = {} local attributZones = {}
for aName,aZone in pairs(testZones) do for aName,aZone in pairs(testZones) do
local attr = cfxZones.getZoneProperty(aZone, attributeName) local attr = cfxZones.getZoneProperty(aZone, attributeName)
@ -808,14 +716,11 @@ function cfxZones.getZonesWithAttributeNamed(attributeName, testZones)
end end
return attributZones return attributZones
end end
-- --
-- zone volume management -- zone volume management
-- --
function cfxZones.getZoneVolume(theZone) function cfxZones.getZoneVolume(theZone)
if not theZone then return nil end if not theZone then return nil end
if (theZone.isCircle) then if (theZone.isCircle) then
-- create a sphere volume -- create a sphere volume
local p = cfxZones.getPoint(theZone) local p = cfxZones.getPoint(theZone)
@ -844,7 +749,6 @@ function cfxZones.getZoneVolume(theZone)
upperRight.x = theZone.bounds.ur.x upperRight.x = theZone.bounds.ur.x
upperRight.z = theZone.bounds.ur.z upperRight.z = theZone.bounds.ur.z
upperRight.y = alt -- we go higher upperRight.y = alt -- we go higher
-- construct volume -- construct volume
local vol = { local vol = {
id = world.VolumeType.BOX, id = world.VolumeType.BOX,
@ -863,7 +767,6 @@ function dmlZone:getZoneVolume()
return cfxZones.getZoneVolume(self) return cfxZones.getZoneVolume(self)
end end
function cfxZones.declutterZone(theZone) function cfxZones.declutterZone(theZone)
if not theZone then return end if not theZone then return end
local theVol = cfxZones.getZoneVolume(theZone) local theVol = cfxZones.getZoneVolume(theZone)
@ -874,7 +777,6 @@ function dmlZone:declutterZone()
local theVol = cfxZones.getZoneVolume(self) local theVol = cfxZones.getZoneVolume(self)
world.removeJunk(theVol) world.removeJunk(theVol)
end end
-- --
-- units / groups in zone -- units / groups in zone
-- --
@ -941,7 +843,6 @@ function dmlZone:allStaticsInZone(useOrigin)
return cfxZones.allStaticsInZone(self, useOrigin) return cfxZones.allStaticsInZone(self, useOrigin)
end end
function cfxZones.groupsOfCoalitionPartiallyInZone(coal, theZone, categ) -- categ is optional function cfxZones.groupsOfCoalitionPartiallyInZone(coal, theZone, categ) -- categ is optional
local groupsInZone = {} local groupsInZone = {}
local allGroups = coalition.getGroups(coal, categ) local allGroups = coalition.getGroups(coal, categ)
@ -992,11 +893,9 @@ end
function dmlZone:isEntireGroupInZone(aGroup) function dmlZone:isEntireGroupInZone(aGroup)
return cfxZones.isEntireGroupInZone(aGroup, self) return cfxZones.isEntireGroupInZone(aGroup, self)
end end
-- --
-- Zone Manipulation -- Zone Manipulation
-- --
function cfxZones.offsetZone(theZone, dx, dz) function cfxZones.offsetZone(theZone, dx, dz)
-- first, update center -- first, update center
theZone.point.x = theZone.point.x + dx theZone.point.x = theZone.point.x + dx
@ -1025,7 +924,6 @@ function dmlZone:offsetZone(dx, dz)
cfxZones.offsetZone(self, dx, dz) cfxZones.offsetZone(self, dx, dz)
end end
function cfxZones.moveZoneTo(theZone, x, z) function cfxZones.moveZoneTo(theZone, x, z)
local dx = x - theZone.point.x local dx = x - theZone.point.x
local dz = z - theZone.point.z local dz = z - theZone.point.z
@ -1046,7 +944,6 @@ function dmlZone:centerZoneOnUnit(theUnit)
self:moveZoneTo(thePoint.x, thePoint.z) self:moveZoneTo(thePoint.x, thePoint.z)
end end
function cfxZones.dumpZones(zoneTable) function cfxZones.dumpZones(zoneTable)
if not zoneTable then zoneTable = cfxZones.zones end if not zoneTable then zoneTable = cfxZones.zones end
@ -1064,15 +961,12 @@ end
function cfxZones.keysForTable(theTable) function cfxZones.keysForTable(theTable)
local keyset={} local keyset={}
local n=0 local n=0
for k,v in pairs(tab) do for k,v in pairs(tab) do
n=n+1 n=n+1
keyset[n]=k keyset[n]=k
end end
return keyset return keyset
end end
-- --
-- return all zones that have a specific named property -- return all zones that have a specific named property
-- --
@ -1091,7 +985,6 @@ function cfxZones.zonesWithProperty(propertyName, searchSet)
end end
return theZones return theZones
end end
-- --
-- return all zones from the zone table that begin with string prefix -- return all zones from the zone table that begin with string prefix
-- --
@ -1107,7 +1000,6 @@ function cfxZones.zonesStartingWithName(prefix, searchSet)
return prefixZones return prefixZones
end end
-- --
-- return all zones from the zone table that begin with the string or set of strings passed in prefix -- return all zones from the zone table that begin with the string or set of strings passed in prefix
-- if you pass 'true' as second (optional) parameter, it will first look for all zones that begin -- if you pass 'true' as second (optional) parameter, it will first look for all zones that begin
@ -1116,7 +1008,6 @@ end
function cfxZones.zonesStartingWith(prefix, searchSet, debugging) function cfxZones.zonesStartingWith(prefix, searchSet, debugging)
-- you can force zones by having their name start with "+" -- you can force zones by having their name start with "+"
-- which will force them to return immediately if debugging is true for this call -- which will force them to return immediately if debugging is true for this call
if (debugging) then if (debugging) then
local debugZones = cfxZones.zonesStartingWithName("+", searchSet) local debugZones = cfxZones.zonesStartingWithName("+", searchSet)
if not (next(debugZones) == nil) then -- # operator only works on array elements if not (next(debugZones) == nil) then -- # operator only works on array elements
@ -1124,7 +1015,6 @@ function cfxZones.zonesStartingWith(prefix, searchSet, debugging)
return debugZones return debugZones
end end
end end
if (type(prefix) == "string") then if (type(prefix) == "string") then
return cfxZones.zonesStartingWithName(prefix, searchSet) return cfxZones.zonesStartingWithName(prefix, searchSet)
end end
@ -1139,7 +1029,6 @@ function cfxZones.zonesStartingWith(prefix, searchSet, debugging)
allZones[zName] = zInfo -- will also replace doublets allZones[zName] = zInfo -- will also replace doublets
end end
end end
return allZones return allZones
end end
@ -1158,13 +1047,11 @@ function cfxZones.getZonesContainingString(aString, searchSet)
resultSet[zName] = zData resultSet[zName] = zData
end end
end end
end; end;
-- filter zones by range to a point. returns indexed set -- filter zones by range to a point. returns indexed set
function cfxZones.getZonesInRange(point, range, theZones) function cfxZones.getZonesInRange(point, range, theZones)
if not theZones then theZones = cfxZones.zones end if not theZones then theZones = cfxZones.zones end
local inRangeSet = {} local inRangeSet = {}
for zName, zData in pairs (theZones) do for zName, zData in pairs (theZones) do
if dcsCommon.dist(point, zData.point) < range then if dcsCommon.dist(point, zData.point) < range then
@ -1216,43 +1103,41 @@ function cfxZones.getZoneByIndex(theZones, theIndex)
end end
-- place a smoke marker in center of zone, offset by dx, dy -- place a smoke marker in center of zone, offset by dx, dy
function cfxZones.markZoneWithSmoke(theZone, dx, dz, smokeColor, alt) function cfxZones.markZoneWithSmoke(theZone, dx, dz, smokeColor, alt, name)
if not alt then alt = 5 end if not alt then alt = 5 end
local point = cfxZones.getPoint(theZone) --{} -- theZone.point local point = cfxZones.getPoint(theZone) --{} -- theZone.point
point.x = point.x + dx -- getpoint updates and returns copy point.x = point.x + dx -- getpoint updates and returns copy
point.z = point.z + dz point.z = point.z + dz
-- get height at point -- correct height at point
point.y = land.getHeight({x = point.x, y = point.z}) + alt point.y = land.getHeight({x = point.x, y = point.z}) + alt
-- height-correct trigger.action.smoke(point, smokeColor, name)
--local newPoint= {x = point.x, y = land.getHeight({x = point.x, y = point.z}) + 3, z= point.z}
trigger.action.smoke(point, smokeColor)
end end
function dmlZone:markZoneWithSmoke(dx, dz, smokeColor, alt) function dmlZone:markZoneWithSmoke(dx, dz, smokeColor, alt, name)
cfxZones.markZoneWithSmoke(self, dx, dz, smokeColor, alt) cfxZones.markZoneWithSmoke(self, dx, dz, smokeColor, alt, name)
end end
-- place a smoke marker in center of zone, offset by radius and degrees -- place a smoke marker in center of zone, offset by radius and degrees
function cfxZones.markZoneWithSmokePolar(theZone, radius, degrees, smokeColor, alt) function cfxZones.markZoneWithSmokePolar(theZone, radius, degrees, smokeColor, alt, name)
local rads = degrees * math.pi / 180 local rads = degrees * math.pi / 180
local dx = radius * math.sin(rads) local dx = radius * math.sin(rads)
local dz = radius * math.cos(rads) local dz = radius * math.cos(rads)
cfxZones.markZoneWithSmoke(theZone, dx, dz, smokeColor, alt) cfxZones.markZoneWithSmoke(theZone, dx, dz, smokeColor, alt, name)
end end
function dmlZone:markZoneWithSmokePolar(radius, degrees, smokeColor, alt) function dmlZone:markZoneWithSmokePolar(radius, degrees, smokeColor, alt, name)
cfxZones.markZoneWithSmokePolar(self, radius, degrees, smokeColor, alt) cfxZones.markZoneWithSmokePolar(self, radius, degrees, smokeColor, alt, name)
end end
-- place a smoke marker in center of zone, offset by radius and randomized degrees -- place a smoke marker in center of zone, offset by radius and randomized degrees
function cfxZones.markZoneWithSmokePolarRandom(theZone, radius, smokeColor) function cfxZones.markZoneWithSmokePolarRandom(theZone, radius, smokeColor, name)
local degrees = math.random(360) local degrees = math.random(360)
cfxZones.markZoneWithSmokePolar(theZone, radius, degrees, smokeColor) cfxZones.markZoneWithSmokePolar(theZone, radius, degrees, smokeColor, name)
end end
function dmlZone:markZoneWithSmokePolarRandom(radius, smokeColor) function dmlZone:markZoneWithSmokePolarRandom(radius, smokeColor, name)
local degrees = math.random(360) local degrees = math.random(360)
self:markZoneWithSmokePolar(radius, degrees, smokeColor) self:markZoneWithSmokePolar(radius, degrees, smokeColor, name)
end end
function cfxZones.pointInOneOfZones(thePoint, zoneArray, useOrig) function cfxZones.pointInOneOfZones(thePoint, zoneArray, useOrig)
@ -1264,17 +1149,13 @@ function cfxZones.pointInOneOfZones(thePoint, zoneArray, useOrig)
return false, 0, 0, nil return false, 0, 0, nil
end end
-- unitInZone returns true if theUnit is inside the zone -- unitInZone returns true if theUnit is inside the zone
-- the second value returned is the percentage of distance -- the second value returned is the percentage of distance
-- from center to rim, with 100% being entirely in center, 0 = outside -- from center to rim, with 100% being entirely in center, 0 = outside
-- the third value returned is the distance to center -- the third value returned is the distance to center
function cfxZones.pointInZone(thePoint, theZone, useOrig) function cfxZones.pointInZone(thePoint, theZone, useOrig)
if not (theZone) then return false, 0, 0 end if not (theZone) then return false, 0, 0 end
local pflat = {x = thePoint.x, y = 0, z = thePoint.z} local pflat = {x = thePoint.x, y = 0, z = thePoint.z}
local zpoint local zpoint
if useOrig then if useOrig then
zpoint = cfxZones.getDCSOrigin(theZone) zpoint = cfxZones.getDCSOrigin(theZone)
@ -1284,26 +1165,22 @@ function cfxZones.pointInZone(thePoint, theZone, useOrig)
local ppoint = thePoint -- xyz local ppoint = thePoint -- xyz
local pflat = {x = ppoint.x, y = 0, z = ppoint.z} local pflat = {x = ppoint.x, y = 0, z = ppoint.z}
local dist = dcsCommon.dist(zpoint, pflat) local dist = dcsCommon.dist(zpoint, pflat)
if theZone.isCircle then if theZone.isCircle then
if theZone.radius <= 0 then if theZone.radius <= 0 then
return false, 0, 0 return false, 0, 0
end end
local success = dist < theZone.radius local success = dist < theZone.radius
local percentage = 0 local percentage = 0
if (success) then if (success) then
percentage = 1 - dist / theZone.radius percentage = 1 - dist / theZone.radius
end end
return success, percentage, dist return success, percentage, dist
elseif theZone.isPoly then elseif theZone.isPoly then
local success = cfxZones.isPointInsidePoly(pflat, theZone.poly) local success = cfxZones.isPointInsidePoly(pflat, theZone.poly)
return success, 0, dist return success, 0, dist
else else
trigger.action.outText("pointInZone: Unknown zone type for " .. theZone.name, 10) trigger.action.outText("pointInZone: Unknown zone type for " .. theZone.name, 10)
end end
return false return false
end end
@ -1311,7 +1188,6 @@ function dmlZone:pointInZone(thePoint, useOrig)
return cfxZones.pointInZone(thePoint, self, useOrig) return cfxZones.pointInZone(thePoint, self, useOrig)
end end
function cfxZones.unitInZone(theUnit, theZone) function cfxZones.unitInZone(theUnit, theZone)
if not (theUnit) then return false, 0, 0 end if not (theUnit) then return false, 0, 0 end
if not (theUnit:isExist()) then return false, 0, 0 end if not (theUnit:isExist()) then return false, 0, 0 end
@ -1383,7 +1259,6 @@ end
function cfxZones.growZone() function cfxZones.growZone()
-- circular zones simply increase radius -- circular zones simply increase radius
-- poly zones: not defined -- poly zones: not defined
end end
@ -1394,7 +1269,6 @@ function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName,
if not drivable then drivable = false end if not drivable then drivable = false end
-- group name will be taken from zone name and prependend with "G_" -- group name will be taken from zone name and prependend with "G_"
local theGroup = dcsCommon.createGroundGroupWithUnits(groupName, theUnits, theZone.radius, nil, formation, nil, liveries) local theGroup = dcsCommon.createGroundGroupWithUnits(groupName, theUnits, theZone.radius, nil, formation, nil, liveries)
theGroup.uncontrollable = false -- just for completeness theGroup.uncontrollable = false -- just for completeness
if drivable then if drivable then
local units = theGroup.units local units = theGroup.units
@ -1402,34 +1276,26 @@ function cfxZones.createGroundUnitsInZoneForCoalition (theCoalition, groupName,
theUnit.playerCanDrive = drivable theUnit.playerCanDrive = drivable
end end
end end
-- turn the entire formation to heading -- turn the entire formation to heading
if (not heading) then heading = 0 end if (not heading) then heading = 0 end
dcsCommon.rotateGroupData(theGroup, heading) -- currently, group is still at origin, no cx, cy dcsCommon.rotateGroupData(theGroup, heading) -- currently, group is still at origin, no cx, cy
-- now move the group to center of theZone -- now move the group to center of theZone
dcsCommon.moveGroupDataTo(theGroup, dcsCommon.moveGroupDataTo(theGroup,
theZone.point.x, theZone.point.x,
theZone.point.z) -- watchit: Z!!! theZone.point.z) -- watchit: Z!!!
-- create the group in the world and return it -- create the group in the world and return it
-- first we need to translate the coalition to a legal -- first we need to translate the coalition to a legal
-- country. we use UN for neutral, cjtf for red and blue -- country. we use UN for neutral, cjtf for red and blue
local theSideCJTF = dcsCommon.coalition2county(theCoalition) local theSideCJTF = dcsCommon.coalition2county(theCoalition)
-- store cty and cat for later access. DCS doesn't need it, but we may -- store cty and cat for later access. DCS doesn't need it, but we may
theGroup.cty = theSideCJTF theGroup.cty = theSideCJTF
theGroup.cat = Group.Category.GROUND theGroup.cat = Group.Category.GROUND
-- create a copy of the group data for -- create a copy of the group data for
-- later reference -- later reference
local groupDataCopy = dcsCommon.clone(theGroup) local groupDataCopy = dcsCommon.clone(theGroup)
local newGroup = coalition.addGroup(theSideCJTF, Group.Category.GROUND, theGroup) local newGroup = coalition.addGroup(theSideCJTF, Group.Category.GROUND, theGroup)
return newGroup, groupDataCopy return newGroup, groupDataCopy
end end
-- --
-- =============== -- ===============
-- FLAG PROCESSING -- FLAG PROCESSING
@ -1456,7 +1322,6 @@ function cfxZones.pulseFlag(theFlag, method, theZone)
end end
local newVal = 1 local newVal = 1
cfxZones.setFlagValue(theFlag, newVal, theZone) cfxZones.setFlagValue(theFlag, newVal, theZone)
-- schedule second half of pulse -- schedule second half of pulse
timer.scheduleFunction(cfxZones.unPulseFlag, args, timer.getTime() + delay) timer.scheduleFunction(cfxZones.unPulseFlag, args, timer.getTime() + delay)
end end
@ -1489,7 +1354,6 @@ function cfxZones.evalRemainder(remainder, theZone)
remainder = string.sub(remainder, 2) remainder = string.sub(remainder, 2)
remainder = dcsCommon.trim(remainder) remainder = dcsCommon.trim(remainder)
end end
if esc == "(" and last == ")" and string.len(remainder) > 2 then if esc == "(" and last == ")" and string.len(remainder) > 2 then
-- note: iisues with startswith("(") ??? -- note: iisues with startswith("(") ???
remainder = string.sub(remainder, 2, -2) remainder = string.sub(remainder, 2, -2)
@ -1519,7 +1383,6 @@ function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
if not theZone then if not theZone then
trigger.action.outText("+++zones: nil theZone on pollFlag", 30) trigger.action.outText("+++zones: nil theZone on pollFlag", 30)
end end
local mt = type(method) local mt = type(method)
if mt == "number" then if mt == "number" then
method = "#" .. method -- convert to immediate method = "#" .. method -- convert to immediate
@ -1528,7 +1391,6 @@ function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
trigger.action.outText("+++zne: warning: zone <" .. theZone.name .. "> method type <" .. mt .. "> received. Ignoring", 30) trigger.action.outText("+++zne: warning: zone <" .. theZone.name .. "> method type <" .. mt .. "> received. Ignoring", 30)
return return
end end
local val = nil local val = nil
method = method:lower() method = method:lower()
method = dcsCommon.trim(method) method = dcsCommon.trim(method)
@ -1549,7 +1411,6 @@ function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
return return
else else
end end
if dcsCommon.stringStartsWith(method, "#") then if dcsCommon.stringStartsWith(method, "#") then
-- immediate value command. remove # and eval remainder -- immediate value command. remove # and eval remainder
local remainder = dcsCommon.removePrefix(method, "#") local remainder = dcsCommon.removePrefix(method, "#")
@ -1560,30 +1421,19 @@ function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
end end
return return
end end
local currVal = cfxZones.getFlagValue(theFlag, theZone) local currVal = cfxZones.getFlagValue(theFlag, theZone)
if method == "inc" or method == "f+1" then if method == "inc" or method == "f+1" then
--trigger.action.setUserFlag(theFlag, currVal + 1)
cfxZones.setFlagValue(theFlag, currVal+1, theZone) cfxZones.setFlagValue(theFlag, currVal+1, theZone)
elseif method == "dec" or method == "f-1" then elseif method == "dec" or method == "f-1" then
-- trigger.action.setUserFlag(theFlag, currVal - 1)
cfxZones.setFlagValue(theFlag, currVal-1, theZone) cfxZones.setFlagValue(theFlag, currVal-1, theZone)
elseif method == "off" or method == "f=0" then elseif method == "off" or method == "f=0" then
-- trigger.action.setUserFlag(theFlag, 0)
cfxZones.setFlagValue(theFlag, 0, theZone) cfxZones.setFlagValue(theFlag, 0, theZone)
elseif method == "flip" or method == "xor" then elseif method == "flip" or method == "xor" then
if currVal ~= 0 then if currVal ~= 0 then
-- trigger.action.setUserFlag(theFlag, 0)
cfxZones.setFlagValue(theFlag, 0, theZone) cfxZones.setFlagValue(theFlag, 0, theZone)
else else
--trigger.action.setUserFlag(theFlag, 1)
cfxZones.setFlagValue(theFlag, 1, theZone) cfxZones.setFlagValue(theFlag, 1, theZone)
end end
elseif dcsCommon.stringStartsWith(method, "pulse") then elseif dcsCommon.stringStartsWith(method, "pulse") then
cfxZones.pulseFlag(theFlag, method, theZone) cfxZones.pulseFlag(theFlag, method, theZone)
@ -1595,22 +1445,18 @@ function cfxZones.doPollFlag(theFlag, method, theZone) -- no OOP equivalent
if theZone.verbose then if theZone.verbose then
trigger.action.outText("+++zones: (poll) updating with '+' flag <" .. theFlag .. "> in <" .. theZone.name .. "> by <" .. adder .. "> to <" .. adder + currVal .. ">", 30) trigger.action.outText("+++zones: (poll) updating with '+' flag <" .. theFlag .. "> in <" .. theZone.name .. "> by <" .. adder .. "> to <" .. adder + currVal .. ">", 30)
end end
elseif dcsCommon.stringStartsWith(method, "-") then elseif dcsCommon.stringStartsWith(method, "-") then
-- we subtract whatever is to the right -- we subtract whatever is to the right
local remainder = dcsCommon.removePrefix(method, "-") local remainder = dcsCommon.removePrefix(method, "-")
local adder = cfxZones.evalRemainder(remainder) local adder = cfxZones.evalRemainder(remainder)
cfxZones.setFlagValue(theFlag, currVal-adder, theZone) cfxZones.setFlagValue(theFlag, currVal-adder, theZone)
else else
if method ~= "on" and method ~= "f=1" then if method ~= "on" and method ~= "f=1" then
trigger.action.outText("+++zones: unknown method <" .. method .. "> for flag <" .. theFlag .. "> in zone <" .. theZone.name .. "> - setting to 1", 30) trigger.action.outText("+++zones: unknown method <" .. method .. "> for flag <" .. theFlag .. "> in zone <" .. theZone.name .. "> - setting to 1", 30)
end end
-- default: on. -- default: on.
-- trigger.action.setUserFlag(theFlag, 1)
cfxZones.setFlagValue(theFlag, 1, theZone) cfxZones.setFlagValue(theFlag, 1, theZone)
end end
if cfxZones.verbose then if cfxZones.verbose then
local newVal = cfxZones.getFlagValue(theFlag, theZone) local newVal = cfxZones.getFlagValue(theFlag, theZone)
trigger.action.outText("+++zones: flag <" .. theFlag .. "> changed from " .. currVal .. " to " .. newVal, 30) trigger.action.outText("+++zones: flag <" .. theFlag .. "> changed from " .. currVal .. " to " .. newVal, 30)
@ -1620,18 +1466,14 @@ end
function cfxZones.pollFlag(theFlag, method, theZone) function cfxZones.pollFlag(theFlag, method, theZone)
local allFlags = {} local allFlags = {}
if dcsCommon.containsString(theFlag, ",") then if dcsCommon.containsString(theFlag, ",") then
if cfxZones.verbose then if cfxZones.verbose then trigger.action.outText("+++zones: will poll flag set <" .. theFlag .. "> with " .. method, 30) end
trigger.action.outText("+++zones: will poll flag set <" .. theFlag .. "> with " .. method, 30)
end
allFlags = dcsCommon.splitString(theFlag, ",") allFlags = dcsCommon.splitString(theFlag, ",")
else else
table.insert(allFlags, theFlag) table.insert(allFlags, theFlag)
end end
for idx, aFlag in pairs(allFlags) do for idx, aFlag in pairs(allFlags) do
aFlag = dcsCommon.trim(aFlag) aFlag = dcsCommon.trim(aFlag)
-- note: mey require range preprocessing, but that's not -- note: mey require range preprocessing, but that's not a priority
-- a priority
cfxZones.doPollFlag(aFlag, method, theZone) cfxZones.doPollFlag(aFlag, method, theZone)
end end
end end
@ -1646,19 +1488,16 @@ function cfxZones.expandFlagName(theFlag, theZone)
if theZone then if theZone then
zoneName = theZone.name -- for flag wildcards zoneName = theZone.name -- for flag wildcards
end end
if type(theFlag) == "number" then if type(theFlag) == "number" then
-- straight number, return -- straight number, return
return theFlag return theFlag
end end
-- we assume it's a string now -- we assume it's a string now
theFlag = dcsCommon.trim(theFlag) -- clear leading/trailing spaces theFlag = dcsCommon.trim(theFlag) -- clear leading/trailing spaces
local nFlag = tonumber(theFlag) local nFlag = tonumber(theFlag)
if nFlag then -- a number, legal if nFlag then -- a number, legal
return theFlag return theFlag
end end
-- now do wildcard processing. we have alphanumeric -- now do wildcard processing. we have alphanumeric
if dcsCommon.stringStartsWith(theFlag, "*") then if dcsCommon.stringStartsWith(theFlag, "*") then
theFlag = zoneName .. theFlag theFlag = zoneName .. theFlag
@ -1677,9 +1516,7 @@ end
function cfxZones.setFlagValueMult(theFlag, theValue, theZone) function cfxZones.setFlagValueMult(theFlag, theValue, theZone)
local allFlags = {} local allFlags = {}
if dcsCommon.containsString(theFlag, ",") then if dcsCommon.containsString(theFlag, ",") then
if cfxZones.verbose then if cfxZones.verbose then trigger.action.outText("+++zones: will multi-set flags <" .. theFlag .. "> to " .. theValue, 30)end
trigger.action.outText("+++zones: will multi-set flags <" .. theFlag .. "> to " .. theValue, 30)
end
allFlags = dcsCommon.splitString(theFlag, ",") allFlags = dcsCommon.splitString(theFlag, ",")
else else
table.insert(allFlags, theFlag) table.insert(allFlags, theFlag)
@ -1687,8 +1524,7 @@ function cfxZones.setFlagValueMult(theFlag, theValue, theZone)
for idx, aFlag in pairs(allFlags) do for idx, aFlag in pairs(allFlags) do
aFlag = dcsCommon.trim(aFlag) aFlag = dcsCommon.trim(aFlag)
-- note: mey require range preprocessing, but that's not -- note: mey require range preprocessing, but that's not a priority
-- a priority
cfxZones.doSetFlagValue(aFlag, theValue, theZone) cfxZones.doSetFlagValue(aFlag, theValue, theZone)
end end
end end
@ -1700,7 +1536,6 @@ function cfxZones.doSetFlagValue(theFlag, theValue, theZone)
else else
zoneName = theZone.name -- for flag wildcards zoneName = theZone.name -- for flag wildcards
end end
if type(theFlag) == "number" then if type(theFlag) == "number" then
-- straight set, oldschool ME flag -- straight set, oldschool ME flag
trigger.action.setUserFlag(theFlag, theValue) trigger.action.setUserFlag(theFlag, theValue)
@ -1730,7 +1565,6 @@ function cfxZones.getFlagValue(theFlag, theZone)
else else
zoneName = theZone.name -- for flag wildcards zoneName = theZone.name -- for flag wildcards
end end
if type(theFlag) == "number" then if type(theFlag) == "number" then
-- straight get, ME flag -- straight get, ME flag
return tonumber(trigger.misc.getUserFlag(theFlag)) return tonumber(trigger.misc.getUserFlag(theFlag))
@ -1750,7 +1584,7 @@ function cfxZones.getFlagValue(theFlag, theZone)
-- now do wildcard processing. we have alphanumeric -- now do wildcard processing. we have alphanumeric
if dcsCommon.stringStartsWith(theFlag, "*") then if dcsCommon.stringStartsWith(theFlag, "*") then
theFlag = zoneName .. theFlag theFlag = zoneName .. theFlag
end end
return tonumber(trigger.misc.getUserFlag(theFlag)) return tonumber(trigger.misc.getUserFlag(theFlag))
end end
@ -2089,7 +1923,8 @@ function cfxZones.drawZone(theZone, lineColor, fillColor, markID)
if not lineColor then lineColor = {0.8, 0.8, 0.8, 1.0} end if not lineColor then lineColor = {0.8, 0.8, 0.8, 1.0} end
if not fillColor then fillColor = {0.8, 0.8, 0.8, 0.0} end if not fillColor then fillColor = {0.8, 0.8, 0.8, 0.0} end
if not markID then markID = dcsCommon.numberUUID() end if not markID then markID = dcsCommon.numberUUID() end
-- trigger.action.outText("drawZone <" .. theZone.name .. "> - L: r=" .. lineColor[1] .. ", g= " .. lineColor[2] .. ", b=" .. lineColor[3] .. ", a=" .. lineColor[4] .. ".", 30)
-- trigger.action.outText("and fill F: r=" .. fillColor[1] .. ", g= " .. fillColor[2] .. ", b=" .. fillColor[3] .. ", a=" .. fillColor[4] .. ".", 30)
if theZone.isCircle then if theZone.isCircle then
trigger.action.circleToAll(-1, markID, theZone.point, theZone.radius, lineColor, fillColor, 1, true, "") trigger.action.circleToAll(-1, markID, theZone.point, theZone.radius, lineColor, fillColor, 1, true, "")
else else
@ -2803,6 +2638,8 @@ function cfxZones.getSmokeColorStringFromZoneProperty(theZone, theProperty, defa
local s = cfxZones.getStringFromZoneProperty(theZone, theProperty, default) local s = cfxZones.getStringFromZoneProperty(theZone, theProperty, default)
s = s:lower() s = s:lower()
s = dcsCommon.trim(s) s = dcsCommon.trim(s)
if s == "?" or s == "rnd" or s == "random" then s = tostring(dcsCommon.smallRandom(5) - 1) end
-- check numbers -- check numbers
if (s == "0") then return "green" end if (s == "0") then return "green" end
if (s == "1") then return "red" end if (s == "1") then return "red" end
@ -2831,6 +2668,7 @@ function cfxZones.getSmokeColorNumberFromZoneProperty(theZone, theProperty, defa
s = s:lower() s = s:lower()
s = dcsCommon.trim(s) s = dcsCommon.trim(s)
if s == "?" or s == "rnd" or s == "random" then s = tostring(dcsCommon.smallRandom(5) - 1) end
-- check numbers -- check numbers
local n = tonumber(s) local n = tonumber(s)
if n then if n then

View File

@ -1,5 +1,5 @@
cloneZones = {} cloneZones = {}
cloneZones.version = "2.5.2" cloneZones.version = "2.6.0"
cloneZones.verbose = false cloneZones.verbose = false
cloneZones.requiredLibs = { cloneZones.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -61,6 +61,8 @@ cloneZones.respawnOnGroupID = true
empty! detection through saves (missed hasClones) empty! detection through saves (missed hasClones)
2.5.1 - f? and in? put on notice for depreciation 2.5.1 - f? and in? put on notice for depreciation
2.5.2 - removed bug when checking damaged! and no units cloned 2.5.2 - removed bug when checking damaged! and no units cloned
2.6.0 - maxCycles attribute
- increased vorbosity during persistance:loadData
--]]-- --]]--
-- --
@ -332,6 +334,14 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
if theZone:hasProperty("health#") then if theZone:hasProperty("health#") then
theZone.health = theZone:getStringFromZoneProperty("health#") theZone.health = theZone:getStringFromZoneProperty("health#")
end end
-- maxCycles
if theZone:hasProperty("maxCycles") then
theZone.maxCycles = theZone:getNumberFromZoneProperty("maxCycles", 99999)
end
if theZone:hasProperty("maxCycle") then
theZone.maxCycles = theZone:getNumberFromZoneProperty("maxCycle", 99999)
end
-- we end with clear plate -- we end with clear plate
theZone.lastSize = 0 -- no units here theZone.lastSize = 0 -- no units here
end end
@ -1506,6 +1516,18 @@ function cloneZones.spawnWithCloner(theZone)
end end
end end
-- see if we have spawned enough
if theZone.maxCycles then
if theZone.maxCycles > 0 then -- update and spawn
theZone.maxCycles = theZone.maxCycles - 1
else -- no spawn
if theZone.verbose then
trigger.action.outText("+++clnZ: cloner <" .. theZone.name .. "> out of cycles. No clone cycle", 30)
end
return -- no spawning, out of cycles
end
end
-- force spawn with this spawner -- force spawn with this spawner
local templateZone = theZone local templateZone = theZone
if theZone.source then if theZone.source then
@ -1903,6 +1925,9 @@ function cloneZones.saveData()
local cData = {} local cData = {}
local cName = theCloner.name local cName = theCloner.name
cData.myUniqueCounter = theCloner.myUniqueCounter cData.myUniqueCounter = theCloner.myUniqueCounter
if theCloner.maxCycles then
cData.maxCycles = theCloner.maxCycles
end
cData.oSize = theCloner.oSize cData.oSize = theCloner.oSize
cData.lastSize = theCloner.lastSize cData.lastSize = theCloner.lastSize
-- mySpawns: all groups I'm curently observing for empty! -- mySpawns: all groups I'm curently observing for empty!
@ -1946,7 +1971,8 @@ function cloneZones.loadData()
end end
return return
end end
local gSpawn = 0
local uSpawn = 0
-- spawn all units -- spawn all units
local allClones = theData.clones local allClones = theData.clones
for gName, gData in pairs (allClones) do for gName, gData in pairs (allClones) do
@ -1958,12 +1984,15 @@ function cloneZones.loadData()
local gdClone = dcsCommon.clone(gData) local gdClone = dcsCommon.clone(gData)
cloneZones.allClones[gName] = gdClone cloneZones.allClones[gName] = gdClone
local theGroup = coalition.addGroup(cty, cat, gData) local theGroup = coalition.addGroup(cty, cat, gData)
uSpawn = uSpawn + #theGroup:getUnits()
gSpawn = gSpawn + 1
-- turn off AI if disabled -- turn off AI if disabled
if not gData.useAI then if not gData.useAI then
cloneZones.turnOffAI({theGroup}) cloneZones.turnOffAI({theGroup})
end end
end end
if cloneZones.verbose then trigger.action.outText("load: loaded <" .. gSpawn .. "> groups, <" .. uSpawn .. "> units total.", 30) end
-- spawn all static objects -- spawn all static objects
local allObjects = theData.objects local allObjects = theData.objects
for oName, oData in pairs(allObjects) do for oName, oData in pairs(allObjects) do
@ -2001,6 +2030,7 @@ function cloneZones.loadData()
end end
if cData.oSize then theCloner.oSize = cData.oSize end if cData.oSize then theCloner.oSize = cData.oSize end
if cData.lastSize then theCloner.lastSize = cData.lastSize end if cData.lastSize then theCloner.lastSize = cData.lastSize end
if cData.maxCycles then theCloner.maxCycles = cData.maxCycles end
local mySpawns = {} local mySpawns = {}
for idx, aName in pairs(cData.mySpawns) do for idx, aName in pairs(cData.mySpawns) do

View File

@ -1,56 +1,16 @@
csarManager = {} csarManager = {}
csarManager.version = "4.2.1" csarManager.version = "4.3.0"
csarManager.ups = 1 csarManager.ups = 1
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
- 2.3.0 - dmlZones
- onRoad attribute for CSAR mission Zones
- rndLoc support
- triggerMethod support
- 2.3.1 - addPrefix option
- delay asynch OK (message only)
- offset zone on randomized soldier
- smokeDist
- 2.3.2 - DCS 2.9 getCategory() fix
- 3.0.0 - moved mission creation out of update loop into own
- removed cfxPlayer dependency
- new event manager
- no longer single-proccing pilots
- can also handle aircraft - isTroopCarrier
- 3.1.0 - integration with scribe
- expanded internal API: newMissionCB, invoked at addMission
- expanded internal API: removedMissionCB
- added *rnd as option for csarName (requires "names")
- missions are sorted by distance
- mission timeLimit range implemented
- update handles time limit (pickup only)
- inflight status reflects time limit but will not time out
- pickupSound option
- lostSound option
- 3.1.1 - birth clears troopsOnBoard
- 3.2.0 - inPopulated csar option
- clearance csar attribute
- maxTries csar attribute
- 3.2.1 - comsRange attribute when mission times out
- rescueTypes option in config
3.2.2 - reset helicopter weight on birth
- cleanup
3.2.3 - hardening against *accidental* multi-unit player groups.
3.2.4 - pass theZone with missionCreateCB when created from zone
3.2.5 - smoke callbacks
- useRanks option
3.2.6 - inBuiltup analogon to cloner
3.2.7 - createCSARForParachutist now supports optional coa (autoCSAR)
3.3.0 - persistence support
3.4.0 - global timeLimit option in config zone
- fixes expiration bug when persisting data
4.0.0 - support for mainMenu 4.0.0 - support for mainMenu
4.0.1 - increased verbosity 4.0.1 - increased verbosity
- fix for Jul-11 2024 DCS bugs - fix for Jul-11 2024 DCS bugs
4.1.0 - support for DCS 2.9.6 dynamic spawns 4.1.0 - support for DCS 2.9.6 dynamic spawns
4.2.0 - automatically support twn if present 4.2.0 - automatically support twn if present
4.2.1 - added Chinook to csar default set (via common) 4.2.1 - added Chinook to csar default set (via common)
4.3.0 - pilot's smoke can now extinguish immediately
- keepSmoke option
INTEGRATES AUTOMATICALLY WITH playerScore INTEGRATES AUTOMATICALLY WITH playerScore
INTEGRATES WITH LIMITED AIRFRAMES INTEGRATES WITH LIMITED AIRFRAMES
@ -549,6 +509,14 @@ function csarManager.heloLanded(theUnit)
30) 30)
didPickup = true; didPickup = true;
-- handle smoke
if csarManager.keepSmoke then
-- trigger.action.outText("keeping smokeName <" .. theMission.smokeName .. "> running", 30)
else
trigger.action.effectSmokeStop(theMission.smokeName)
-- trigger.action.outText("smokeName <" .. theMission.smokeName .. "> removed", 30)
end
local args = {} local args = {}
args.theName = theMission.name args.theName = theMission.name
args.mySide = mySide args.mySide = mySide
@ -1186,18 +1154,19 @@ function csarManager.update() -- every second
table.insert(csarMission.messagedUnits, uName) -- remember that we messaged them so we don't do again table.insert(csarMission.messagedUnits, uName) -- remember that we messaged them so we don't do again
end end
-- also pop smoke if not popped already, or more than 5 minutes ago -- pop smoke if not popped already, or more than 5 minutes ago
if csarManager.useSmoke and (timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then if csarManager.useSmoke and
(timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then
if csarMission.lastSmokeTime < 0 then if csarMission.lastSmokeTime < 0 then
-- this is the first time that this mission pops smoke -- this is the first time that this mission pops smoke
-- trigger.action.outText("***will invoke smoke cb", 30)
csarManager.invokeSmokeCallbacks(csarMission, uName) csarManager.invokeSmokeCallbacks(csarMission, uName)
else else
--trigger.action.outText("nope smoke's a dope", 30) --trigger.action.outText("nope smoke's a dope", 30)
end end
local smokePoint = dcsCommon.randomPointOnPerimeter( local smokePoint = dcsCommon.randomPointOnPerimeter(
csarManager.smokeDist, csarMission.zone.point.x, csarMission.zone.point.z) csarManager.smokeDist, csarMission.zone.point.x, csarMission.zone.point.z)
dcsCommon.markPointWithSmoke(smokePoint, csarManager.smokeColor) csarMission.smokeName = dcsCommon.markPointWithSmoke(smokePoint, csarManager.smokeColor) -- returns unique smoke name
trigger.action.outText("smokeName <" .. csarMission.smokeName .. "> started", 30)
csarMission.lastSmokeTime = timer.getTime() csarMission.lastSmokeTime = timer.getTime()
end end
@ -1236,6 +1205,13 @@ function csarManager.update() -- every second
table.insert(conf.troopsOnBoard, csarMission) table.insert(conf.troopsOnBoard, csarMission)
csarMission.group:destroy() -- will shut up radio as well csarMission.group:destroy() -- will shut up radio as well
csarMission.group = nil -- no more evacuees csarMission.group = nil -- no more evacuees
-- turn off smoke?
if csarManager.keepSmoke then
-- trigger.action.outText("keeping smokeName <" .. csarMission.smokeName .. "> running", 30)
else
trigger.action.effectSmokeStop(csarMission.smokeName)
-- trigger.action.outText("smokeName <" .. csarMission.smokeName .. "> removed", 30)
end
needsGC = true -- need filtering missions needsGC = true -- need filtering missions
-- now handle weight using cargoSuper -- now handle weight using cargoSuper
@ -1425,8 +1401,7 @@ function csarManager.readCSARZone(theZone)
theZone.numCrew = 1 theZone.numCrew = 1
theZone.csarMapMarker = nil theZone.csarMapMarker = nil
if theZone:hasProperty("timeLimit") then if theZone:hasProperty("timeLimit") then
local tmin, tmax = theZone:getPositiveRangeFromZoneProperty("timeLimit", 1) local tmin, tmax = theZone:getPositiveRangeFromZoneProperty("timeLimit", 1) -- minutes
theZone.timeLimit = {tmin, tmax} theZone.timeLimit = {tmin, tmax}
else else
theZone.timeLimit = nil theZone.timeLimit = nil
@ -1572,6 +1547,7 @@ function csarManager.readConfigZone()
csarManager.ups = theZone:getNumberFromZoneProperty("ups", 1) csarManager.ups = theZone:getNumberFromZoneProperty("ups", 1)
csarManager.useSmoke = theZone:getBoolFromZoneProperty("useSmoke", true) csarManager.useSmoke = theZone:getBoolFromZoneProperty("useSmoke", true)
csarManager.keepSmoke = theZone:getBoolFromZoneProperty("keepSmoke", false)
csarManager.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "blue") csarManager.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "blue")
csarManager.smokeDist = theZone:getNumberFromZoneProperty("smokeDist", 30) csarManager.smokeDist = theZone:getNumberFromZoneProperty("smokeDist", 30)
csarManager.smokeColor = dcsCommon.smokeColor2Num(csarManager.smokeColor) csarManager.smokeColor = dcsCommon.smokeColor2Num(csarManager.smokeColor)
@ -1807,4 +1783,5 @@ end
player that they did not survive the transport player that they did not survive the transport
- randomize smoke color if smoke color has more than one entries - randomize smoke color if smoke color has more than one entries
- ELT freq higher bands?
--]]-- --]]--

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "3.2.0" dcsCommon.version = "3.2.1"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false 3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option - point2text new intsOnly option
@ -39,6 +39,7 @@ dcsCommon.version = "3.2.0"
- cfxZones links to processTimeLocWildCards - cfxZones links to processTimeLocWildCards
- new processAtoBWildCards() - new processAtoBWildCards()
- tons of new wildcards like <eleft> - tons of new wildcards like <eleft>
3.2.1 - markPointWithSmoke supports names and returns names
--]]-- --]]--
-- dcsCommon is a library of common lua functions -- dcsCommon is a library of common lua functions
@ -2650,14 +2651,16 @@ end
end end
function dcsCommon.markPointWithSmoke(p, smokeColor) function dcsCommon.markPointWithSmoke(p, smokeColor, name)
if not smokeColor then smokeColor = 0 end if not smokeColor then smokeColor = 0 end
if not name then name = dcsCommon.uuid("dcsCsmoke") end
local x = p.x local x = p.x
local z = p.z -- do NOT change the point directly local z = p.z -- do NOT change the point directly
-- height-correct -- height-correct
local y = land.getHeight({x = x, y = z}) local y = land.getHeight({x = x, y = z})
local newPoint= {x = x, y = y + 2, z = z} local newPoint= {x = x, y = y + 2, z = z}
trigger.action.smoke(newPoint, smokeColor) trigger.action.smoke(newPoint, smokeColor, name)
return name
end end
-- based on buzzer1977's idea, channel is number, eg in 74X, channel is 74, mode is "X" -- based on buzzer1977's idea, channel is number, eg in 74X, channel is 74, mode is "X"

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {} cfxHeloTroops = {}
cfxHeloTroops.version = "4.2.2" cfxHeloTroops.version = "4.2.3"
cfxHeloTroops.verbose = false cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false cfxHeloTroops.autoPickup = false
@ -22,6 +22,8 @@ cfxHeloTroops.requestRange = 500 -- meters
4.2.1 - increased verbosity 4.2.1 - increased verbosity
- also supports 'pickupRang" for reverse-compatibility with manual typo. - also supports 'pickupRang" for reverse-compatibility with manual typo.
4.2.2 - support for attachTo: 4.2.2 - support for attachTo:
4.2.3 - dropZone supports 'keepWait' attribute
- dropZone supports 'setWait' attribute
--]]-- --]]--
cfxHeloTroops.minTime = 3 -- seconds beween tandings cfxHeloTroops.minTime = 3 -- seconds beween tandings
@ -48,6 +50,8 @@ function cfxHeloTroops.processDropZone(theZone)
theZone.dropMethod = theZone:getStringFromZoneProperty("dropMethod", "inc") theZone.dropMethod = theZone:getStringFromZoneProperty("dropMethod", "inc")
theZone.dropCoa = theZone:getCoalitionFromZoneProperty("coalition", 0) theZone.dropCoa = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.autoDespawn = theZone:getNumberFromZoneProperty("autoDespawn", -1) theZone.autoDespawn = theZone:getNumberFromZoneProperty("autoDespawn", -1)
theZone.keepWait = theZone:getBoolFromZoneProperty("keepWait", false)
theZone.setWait = theZone:getBoolFromZoneProperty("setWait", false)
end end
-- --
@ -704,20 +708,47 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
local moveFormation = conf.troopsOnBoard.moveFormation local moveFormation = conf.troopsOnBoard.moveFormation
local code = conf.troopsOnBoard.code local code = conf.troopsOnBoard.code
local canDrive = conf.troopsOnBoard.canDrive local canDrive = conf.troopsOnBoard.canDrive
local theCoalition = theUnit:getGroup():getCoalition() -- my coa
if not orders then orders = "guard" end if not orders then orders = "guard" end
orders = string.lower(orders) orders = string.lower(orders)
-- order processing: if the orders were pre-pended with "wait-" -- order "wait" processing if not in special drop zone:
-- we now remove that, so after dropping they do what their -- if the orders were pre-pended with "wait-"
-- orders where AFTER being picked up -- remove that, so after dropping they do what their
-- orders where AFTER removing "wait"
-- or if setWait is true, add it
local closestDropZone = cfxZones.getClosestZone(p, cfxHeloTroops.dropzones)
if dcsCommon.stringStartsWith(orders, "wait-") then if dcsCommon.stringStartsWith(orders, "wait-") then
orders = dcsCommon.removePrefix(orders, "wait-") local dropWait = false
trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> revoke 'wait' orders, proceed with <".. orders .. ">", 30) if closestDropZone and closestDropZone:pointInZone(p) then
if closestDropZone.dropCoa == 0 or closestDropZone.dropCoa == theCoalition then
if not closestDropZone.keepWait then dropWait = true end
end
end
-- see if we are in a drop zone
if dropWait then
orders = dcsCommon.removePrefix(orders, "wait-")
trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> revoke 'wait' orders, proceed with <".. orders .. ">", 30)
else trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> keeping 'wait' orders (".. orders .. ")", 30) end
end
if not dcsCommon.stringStartsWith(orders, "wait-") then
local setWait = false
if closestDropZone and closestDropZone:pointInZone(p) then
if closestDropZone.dropCoa == 0 or closestDropZone.dropCoa == theCoalition then
if not closestDropZone.setWait then setWait = true end
end
end
-- see if we are in a drop zone
if setWait then
orders = "wait-" .. orders
trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> added 'wait' orders: <".. orders .. ">", 30)
else trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> keeping orders (".. orders .. ")", 30) end
end end
local chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m radius around choppa local chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m radius around choppa
local theCoalition = theUnit:getGroup():getCoalition() -- make it chopper's COALITION
local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition ( local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition (
theCoalition, theCoalition,
theName, -- group name, may be tracked theName, -- group name, may be tracked
@ -1173,4 +1204,5 @@ if not cfxHeloTroops.start() then
end end
-- TODO: weight when loading troops -- TODO: weight when loading troops
-- TODO: keepWait for dropzones: troops keep their "wait" orders when dropzone has that tag

View File

@ -1,5 +1,5 @@
cfxOwnedZones = {} cfxOwnedZones = {}
cfxOwnedZones.version = "2.4.1" cfxOwnedZones.version = "2.5.0"
cfxOwnedZones.verbose = false cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones" cfxOwnedZones.name = "cfxOwnedZones"
@ -45,6 +45,8 @@ cfxOwnedZones.name = "cfxOwnedZones"
2.3.1 - restored getNearestOwnedZoneToPoint 2.3.1 - restored getNearestOwnedZoneToPoint
2.4.0 - dmlZones masterOwner update 2.4.0 - dmlZones masterOwner update
2.4.1 - conquered flag now correctly guarded in loadData() 2.4.1 - conquered flag now correctly guarded in loadData()
2.5.0 - staggering zone drawing in time during loadData to avoid show-stopping DCS bug in trigger.action.removeMark()
--]]-- --]]--
cfxOwnedZones.requiredLibs = { cfxOwnedZones.requiredLibs = {
@ -125,11 +127,11 @@ function cfxOwnedZones.drawZoneInMap(aZone)
end end
if aZone.title then if aZone.title then
aZone.titleID = aZone:drawText(aZone.title, 18, lineColor, {0, 0, 0, 0}) aZone.titleID = aZone:drawText(aZone.title, 18, lineColor, {0, 0, 0, 0}, nil)
end end
if aZone.hidden then return end if aZone.hidden then return end
aZone.markID = aZone:drawZone(lineColor, fillColor) -- markID aZone.markID = aZone:drawZone(lineColor, fillColor, nil) -- markID
end end
function cfxOwnedZones.getOwnedZoneByName(zName) function cfxOwnedZones.getOwnedZoneByName(zName)
@ -748,7 +750,7 @@ function cfxOwnedZones.loadData()
local theData = persistence.getSavedDataForModule("cfxOwnedZones") local theData = persistence.getSavedDataForModule("cfxOwnedZones")
if not theData then if not theData then
if cfxOwnedZones.verbose then if cfxOwnedZones.verbose then
trigger.action.outText("owdZ: no save date received, skipping.", 30) trigger.action.outText("owdZ: no save data received, skipping.", 30)
end end
return return
end end
@ -757,6 +759,9 @@ function cfxOwnedZones.loadData()
-- flagInfo: module-global flags -- flagInfo: module-global flags
-- attackers: all spawned attackers that we feed to groundTroops -- attackers: all spawned attackers that we feed to groundTroops
local allZoneData = theData.zoneData local allZoneData = theData.zoneData
local now = timer.getTime()
local delay = now + 0.5
for zName, zData in pairs(allZoneData) do for zName, zData in pairs(allZoneData) do
-- access zone -- access zone
local theZone = cfxOwnedZones.getOwnedZoneByName(zName) local theZone = cfxOwnedZones.getOwnedZoneByName(zName)
@ -766,7 +771,10 @@ function cfxOwnedZones.loadData()
theZone:setFlagValue(theZone.conqueredFlag, zData.conquered) theZone:setFlagValue(theZone.conqueredFlag, zData.conquered)
end end
-- update mark in map -- update mark in map
cfxOwnedZones.drawZoneInMap(theZone) -- does this impact performance?
timer.scheduleFunction(cfxOwnedZones.drawZoneInMap, theZone, delay)
delay = delay + 0.5
-- cfxOwnedZones.drawZoneInMap(theZone)
else else
trigger.action.outText("owdZ: load - data mismatch: cannot find zone <" .. zName .. ">, skipping zone.", 30) trigger.action.outText("owdZ: load - data mismatch: cannot find zone <" .. zName .. ">, skipping zone.", 30)
end end

View File

@ -1,5 +1,5 @@
persistence = {} persistence = {}
persistence.version = "3.0.2" persistence.version = "3.1.0"
persistence.ups = 1 -- once every 1 seconds persistence.ups = 1 -- once every 1 seconds
persistence.verbose = false persistence.verbose = false
persistence.active = false persistence.active = false
@ -24,6 +24,8 @@ persistence.requiredLibs = {
code cleanup code cleanup
3.0.2 - more logging 3.0.2 - more logging
vardump to log possible vardump to log possible
3.1.0 - validFor attribute -- timed gc
- persistence deallocs data after validFor seconds
PROVIDES LOAD/SAVE ABILITY TO MODULES PROVIDES LOAD/SAVE ABILITY TO MODULES
PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY
@ -74,12 +76,43 @@ end
-- --
-- registered modules call this to get their data -- registered modules call this to get their data
-- --
function persistence.count(theTable)
if not theTable then return 0 end
local c = 0
for idx, val in pairs(theTable) do
c = c + 1
end
return c
end
function persistence.filter(name) -- debugging
--if true then return false end
--if name == "WHpersistence" then return true end
--if name == "unitPersistence" then return true end
--if name == "cfxPlayerScore" then return true end
--if name == "cfxSSBClient" then return true end
--if name == "cfxSpawnZones" then return true end
--if name == "cfxHeloTroops" then return true end
--if name == "rndFlags" then return true end
--if name == "cfxOwnedZones" then return true end
--if name == "cloneZones" then return true end --*
--if name == "cfxObjectDestructDetector" then return true end
return false
end
function persistence.getSavedDataForModule(name, sharedDataName) function persistence.getSavedDataForModule(name, sharedDataName)
-- if persistence.verbose then
-- trigger.action.outText("+++persistence: enter load for <" .. name .. ">", 30)
-- end
if not persistence.active then return nil end if not persistence.active then return nil end
if not persistence.hasData then return nil end if not persistence.hasData then return nil end
if not persistence.missionData then return end if not persistence.missionData then return nil end
if not sharedDataName then sharedDataName = nil end if not sharedDataName then sharedDataName = nil end
if persistence.filter(name) then
trigger.action.outText("FILTERED persistence for <" .. name .. ">", 30)
return nil
end
if sharedDataName then if sharedDataName then
-- we read from shared data and only revert to -- we read from shared data and only revert to
-- common if we find nothing -- common if we find nothing
@ -90,6 +123,7 @@ function persistence.getSavedDataForModule(name, sharedDataName)
local theData = persistence.loadTable(shFile, true) local theData = persistence.loadTable(shFile, true)
if theData then if theData then
if theData[name] then if theData[name] then
trigger.action.outText("+++persistence: returning SHARED for <" .. name .. ">", 30)
return theData[name] return theData[name]
end end
if persistence.verbose then if persistence.verbose then
@ -102,6 +136,9 @@ function persistence.getSavedDataForModule(name, sharedDataName)
end end
end end
end end
if persistence.verbose then
trigger.action.outText("+++persistence: returning data (" .. persistence.count(persistence.missionData[name]) .. " records) for <" .. name .. ">", 30)
end
return persistence.missionData[name] -- simply get the modules data block return persistence.missionData[name] -- simply get the modules data block
end end
@ -580,12 +617,25 @@ function persistence.readConfigZone()
persistence.saveNotification = theZone:getBoolFromZoneProperty("saveNotification", true) persistence.saveNotification = theZone:getBoolFromZoneProperty("saveNotification", true)
persistence.validFor = theZone:getNumberFromZoneProperty("validFor", 5) -- GC after ... seconds
if persistence.verbose then if persistence.verbose then
trigger.action.outText("+++persistence: read config", 30) trigger.action.outText("+++persistence: read config", 30)
end end
end end
function persistence.GC()
-- destroy loaded mission data
if persistence.missionData then
persistence.missionData = nil
if persistence.verbose then
trigger.action.outText("+++persistence: relinquished loaded data.", 30)
end
end
persistence.hasData = false
end
function persistence.start() function persistence.start()
-- lib check -- lib check
if not dcsCommon.libCheck then if not dcsCommon.libCheck then
@ -720,7 +770,7 @@ function persistence.start()
-- we now see if we can and need load data -- we now see if we can and need load data
persistence.missionStartDataLoad() persistence.missionStartDataLoad()
timer.scheduleFunction(persistence.GC, nil, timer.getTime() + persistence.validFor) -- destroy loaded data after this interval
-- and start updating -- and start updating
persistence.update() persistence.update()

View File

@ -1,5 +1,5 @@
pulseFlags = {} pulseFlags = {}
pulseFlags.version = "2.0.3" pulseFlags.version = "2.0.4"
pulseFlags.verbose = false pulseFlags.verbose = false
pulseFlags.requiredLibs = { pulseFlags.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -8,7 +8,7 @@ pulseFlags.requiredLibs = {
--[[-- --[[--
Pulse Flags: DML module to regularly change a flag Pulse Flags: DML module to regularly change a flag
Copyright 2022 by Christian Franz and cf/x Copyright 2022-2025 by Christian Franz and cf/x
Version History Version History
- 2.0.0 dmlZones / OOP - 2.0.0 dmlZones / OOP
@ -16,6 +16,7 @@ pulseFlags.requiredLibs = {
- 2.0.1 activateZoneFlag now works correctly - 2.0.1 activateZoneFlag now works correctly
- 2.0.2 fixed scheduledTime bug while persisting - 2.0.2 fixed scheduledTime bug while persisting
- 2.0.3 now setting -1 (infinite) as pulses works correctly - 2.0.3 now setting -1 (infinite) as pulses works correctly
- 2.0.4 corrected typo when checking verbosity
--]]-- --]]--
pulseFlags.pulses = {} pulseFlags.pulses = {}
@ -69,7 +70,7 @@ function pulseFlags.createPulseWithZone(theZone)
end end
end end
end end
if theZone.verbose or pulseFlag.verbose then if theZone.verbose or pulseFlags.verbose then
trigger.action.outText("+++pulF: set pulses in <" .. theZone.name .. "> to <" .. theZone.pulses .. ">", 30) trigger.action.outText("+++pulF: set pulses in <" .. theZone.name .. "> to <" .. theZone.pulses .. ">", 30)
end end

View File

@ -1,5 +1,5 @@
reaper = {} reaper = {}
reaper.version = "1.2.0" reaper.version = "1.3.0"
reaper.requiredLibs = { reaper.requiredLibs = {
"dcsCommon", "dcsCommon",
"cfxZones", "cfxZones",
@ -21,8 +21,9 @@ VERSION HISTORY
- completely rewrote scanning method (performance) - completely rewrote scanning method (performance)
- added FAC task - added FAC task
- split task generation from wp generation - split task generation from wp generation
- updated reaper naming, uniqueNames attribute (undocumented) - updated reaper naming, uniqueN ames attribute (undocumented)
1.2.0 - support twn when present 1.2.0 - support twn when present
1.3.0 - new invisible option for drone zone
--]]-- --]]--
@ -78,6 +79,7 @@ function reaper.readReaperZone(theZone)
theZone.cycle = theZone:getStringFromZoneProperty("cycle?", "<none>") theZone.cycle = theZone:getStringFromZoneProperty("cycle?", "<none>")
theZone.cycleVal = theZone:getFlagValue(theZone.cycle) theZone.cycleVal = theZone:getFlagValue(theZone.cycle)
end end
theZone.invisible = theZone:getBoolFromZoneProperty("invisible", false)
theZone.hasSpawned = false theZone.hasSpawned = false
@ -149,7 +151,7 @@ function reaper.spawnForZone(theZone, ack)
-- now create and add waypoints to route -- now create and add waypoints to route
gdata.route.points = {} gdata.route.points = {}
local wp1 = reaper.createInitialWP(left, unit.alt, unit.speed) local wp1 = reaper.createInitialWP(left, unit.alt, unit.speed, theZone.invisible)
gdata.route.points[1] = wp1 gdata.route.points[1] = wp1
local wp2 = dcsCommon.createSimpleRoutePointData(right, unit.alt, unit.speed) local wp2 = dcsCommon.createSimpleRoutePointData(right, unit.alt, unit.speed)
gdata.route.points[2] = wp2 gdata.route.points[2] = wp2
@ -195,75 +197,89 @@ function reaper.cleanUp(theZone)
theZone.theSpot = nil theZone.theSpot = nil
end end
function reaper.createReaperTask(alt, speed, target, theZone) function reaper.createReaperTask(alt, speed, target, theZone, invisible)
local task = { if not invisible then invisible = false end
local task = {
["id"] = "ComboTask", ["id"] = "ComboTask",
["params"] = { ["params"] = {
["tasks"] = { ["tasks"] = {
[1] = { [1] = {
["enabled"] = true,
["auto"] = true,
["id"] = "FAC",
["number"] = 1, ["number"] = 1,
["params"] = ["auto"] = false,
{}, -- end of ["params"] ["id"] = "WrappedAction",
["name"] = "INV",
["enabled"] = true,
["params"] = {
["action"] = {
["id"] = "SetInvisible",
["params"] = {
["value"] = invisible,
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [1] }, -- end of [1]
[2] = { [2] = {
["enabled"] = true, ["enabled"] = true,
["auto"] = true, ["auto"] = true,
["id"] = "WrappedAction", ["id"] = "FAC",
["number"] = 2, ["number"] = 2,
["params"] =
{}, -- end of ["params"]
}, -- end of [2]
[3] = {
["enabled"] = true,
["auto"] = true,
["id"] = "WrappedAction",
["number"] = 3,
["params"] = { ["params"] = {
["action"] = { ["action"] = {
["id"] = "EPLRS", ["id"] = "EPLRS",
["params"] = { ["params"] = {
["value"] = true, ["value"] = true,
["groupId"] = 1, ["groupId"] = 1, -- <- looks bad
}, -- end of ["params"] }, -- end of ["params"]
}, -- end of ["action"] }, -- end of ["action"]
}, -- end of ["params"] }, -- end of ["params"]
}, -- end of [2] }, -- end of [3]
[3] = { [4] = {
["enabled"] = true, ["enabled"] = true,
["auto"] = false, ["auto"] = false,
["id"] = "Orbit", ["id"] = "Orbit",
["number"] = 3, ["number"] = 4,
["params"] = { ["params"] = {
["altitude"] = alt, ["altitude"] = alt,
["pattern"] = "Race-Track", ["pattern"] = "Race-Track",
["speed"] = speed, ["speed"] = speed,
}, -- end of ["params"] }, -- end of ["params"]
}, -- end of [3] }, -- end of [4]
}, -- end of ["tasks"] }, -- end of ["tasks"]
}, -- end of ["params"] }, -- end of ["params"]
} -- end of ["task"] } -- end of ["task"]
if theTarget and theZone then if theTarget and theZone then
-- local gID = theTarget:getGroup():getID()
local gID = theTarget:getID() -- NOTE: theTarget is a GROUP!!!! local gID = theTarget:getID() -- NOTE: theTarget is a GROUP!!!!
local task4 = { local task4 = { -- now task5 after we added invisibility
["enabled"] = true, ["enabled"] = true,
["auto"] = false, ["auto"] = false,
["id"] = "FAC_AttackGroup", ["id"] = "FAC_AttackGroup",
["number"] = 4, ["number"] = 5,
["params"] = ["params"] =
{ {
["number"] = 1, ["number"] = 5,
["designation"] = "No", ["designation"] = "No",
["modulation"] = 0, ["modulation"] = 0,
["groupId"] = gID, ["groupId"] = gID,
-- ["callname"] = 1,
-- ["datalink"] = true,
["weaponType"] = 0, -- 9663676414, ["weaponType"] = 0, -- 9663676414,
["frequency"] = theZone.freq, -- 133000000, ["frequency"] = theZone.freq, -- 133000000,
}, -- end of ["params"] }, -- end of ["params"]
} -- end of [4] } -- end of [5]
task.params.tasks[4] = task4 task.params.tasks[5] = task4
end end
return task return task
end end
function reaper.createInitialWP(p, alt, speed) -- warning: target must be a GROUP function reaper.createInitialWP(p, alt, speed, invisible) -- warning: target must be a GROUP
if not invisible then invisible = false end
local wp = { local wp = {
["alt"] = alt, ["alt"] = alt,
["action"] = "Turning Point", ["action"] = "Turning Point",
@ -282,7 +298,7 @@ function reaper.createInitialWP(p, alt, speed) -- warning: target must be a GROU
["formation_template"] = "", ["formation_template"] = "",
} -- end of wp } -- end of wp
wp.task = reaper.createReaperTask(alt, speed) -- no zone, no target wp.task = reaper.createReaperTask(alt, speed, nil, nil, invisible) -- no zone, no target
return wp return wp
end end
@ -319,7 +335,7 @@ function reaper.setTarget(theZone, theTarget, cycled)
-- now make tracking the group the drone's task -- now make tracking the group the drone's task
local theGroup = theTarget:getGroup() local theGroup = theTarget:getGroup()
local theTask = reaper.createReaperTask(theZone.alt, theZone.speed, theGroup, theZone) -- create full FAC task with orbit and group engage local theTask = reaper.createReaperTask(theZone.alt, theZone.speed, theGroup, theZone, theZone.invisible) -- create full FAC task with orbit and group engage
local theController = theZone.theUav:getController() local theController = theZone.theUav:getController()
if not theController then if not theController then
trigger.action.outText("+++Rpr: UAV has no controller, getting group") trigger.action.outText("+++Rpr: UAV has no controller, getting group")
@ -927,4 +943,5 @@ end
--[[-- --[[--
Idea: mobile launch vehicle, zone follows apc around. Can even be hauled along with hook Idea: mobile launch vehicle, zone follows apc around. Can even be hauled along with hook
todo: make reaper invisible by attribute
--]]-- --]]--

View File

@ -1,108 +1,74 @@
cfxSmokeZone = {} cfxSmokeZone = {}
cfxSmokeZone.version = "2.0.1" cfxSmokeZone.version = "3.0.0"
cfxSmokeZone.requiredLibs = { cfxSmokeZone.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
--[[-- --[[--
Version History Version History
2.0.0 - clean up 3.0.0 - now supports immediate smoke stop
2.0.1 - deprecating "f?" - supports persistence
- code cleanup
--]]-- --]]--
cfxSmokeZone.smokeZones = {} cfxSmokeZone.smokeZones = {}
cfxSmokeZone.updateDelay = 5 * 60 -- every 5 minutes cfxSmokeZone.updateDelay = 5 * 60 -- every 5 minutes
function cfxSmokeZone.processSmokeZone(aZone) function cfxSmokeZone.processSmokeZone(aZone)
local rawVal = aZone:getStringFromZoneProperty("smoke", "green") aZone.smokeColor = aZone:getSmokeColorNumberFromZoneProperty("smoke", "green")--theColor
rawVal = rawVal:lower()
local theColor = 0
if rawVal == "red" or rawVal == "1" then theColor = 1 end
if rawVal == "white" or rawVal == "2" then theColor = 2 end
if rawVal == "orange" or rawVal == "3" then theColor = 3 end
if rawVal == "blue" or rawVal == "4" then theColor = 4 end
if rawVal == "?" or rawVal == "random" or rawVal == "rnd" then
theColor = dcsCommon.smallRandom(5) - 1
end
aZone.smokeColor = theColor
aZone.smokeAlt = aZone:getNumberFromZoneProperty("altitude", 1) aZone.smokeAlt = aZone:getNumberFromZoneProperty("altitude", 1)
aZone.smokeName = aZone.name .. "-s-" .. dcsCommon.numberUUID()
if aZone:hasProperty("alt") then if aZone:hasProperty("alt") then
aZone.smokeAlt = aZone:getNumberFromZoneProperty("alt", 1) aZone.smokeAlt = aZone:getNumberFromZoneProperty("alt", 1)
elseif aZone:hasProperty("agl") then elseif aZone:hasProperty("agl") then
aZone.smokeAlt = aZone:getNumberFromZoneProperty("agl", 1) aZone.smokeAlt = aZone:getNumberFromZoneProperty("agl", 1)
end end
aZone.paused = aZone:getBoolFromZoneProperty("paused", false)
-- paused aZone.onFlag = aZone:getStringFromZoneProperty("startSmoke?", "none")
aZone.paused = aZone:getBoolFromZoneProperty("paused", false)
if aZone:hasProperty("f?") then
aZone.onFlag = aZone:getStringFromZoneProperty("f?", "*<none>")
trigger.action.outText("+++smokeZones: WARNING: smoke zone <" .. aZone.name .. "> uses deprecated attribute 'f?' - use 'startSmoke?' instead.", 30)
elseif aZone:hasProperty("startSmoke?") then
aZone.onFlag = aZone:getStringFromZoneProperty("startSmoke?", "none")
end
if aZone.onFlag then if aZone.onFlag then
aZone.onFlagVal = aZone:getFlagValue(aZone.onFlag) -- save last value aZone.onFlagVal = aZone:getFlagValue(aZone.onFlag) -- save last value
end end
if aZone:hasProperty("stopSmoke?") then if aZone:hasProperty("stopSmoke?") then
aZone.smkStopFlag = aZone:getStringFromZoneProperty("stopSmoke?", "<none>") aZone.smkStopFlag = aZone:getStringFromZoneProperty("stopSmoke?", "<none>")
aZone.smkLastStopFlag = aZone:getFlagValue(aZone.smkStopFlag) aZone.smkLastStopFlag = aZone:getFlagValue(aZone.smkStopFlag)
end end
aZone.smokeTriggerMethod = aZone:getStringFromZoneProperty( "triggerMethod", "change") aZone.smokeTriggerMethod = aZone:getStringFromZoneProperty( "triggerMethod", "change")
if aZone:hasProperty("smokeTriggerMethod") then if aZone:hasProperty("smokeTriggerMethod") then
aZone.smokeTriggerMethod = aZone:getStringFromZoneProperty( "smokeTriggerMethod", "change") aZone.smokeTriggerMethod = aZone:getStringFromZoneProperty( "smokeTriggerMethod", "change")
end end
end end
function cfxSmokeZone.addSmokeZone(aZone) function cfxSmokeZone.addSmokeZone(aZone)
table.insert(cfxSmokeZone.smokeZones, aZone) table.insert(cfxSmokeZone.smokeZones, aZone)
end end
function cfxSmokeZone.addSmokeZoneWithColor(aZone, aColor, anAltitude, paused, onFlag) function cfxSmokeZone.getSmokeZoneNamed(aName)
if not aColor then aColor = 0 end -- default green if not aName then return end
if not anAltitude then anAltitude = 5 end local aName = string.upper(aName)
if not aZone then return end for idx, theZone in pairs(cfxSmokeZone.smokeZones) do
if not paused then paused = false end if theZone.name == aName then return theZone end
end
aZone.smokeColor = aColor return nil
aZone.smokeAlt = anAltitude
aZone.paused = paused
if onFlag then
aZone.onFlag = onFlag
aZone.onFlagVal = cfxZones.getFlagValue(aZone.onFlag, aZone) -- trigger.misc.getUserFlag(onFlag)
end
cfxSmokeZone.addSmokeZone(aZone) -- add to update loop
if not paused then
cfxSmokeZone.startSmoke(aZone)
end
end end
function cfxSmokeZone.startSmoke(aZone) function cfxSmokeZone.startSmoke(aZone)
if type(aZone) == "string" then
aZone = cfxZones.getZoneByName(aZone)
end
if not aZone then return end if not aZone then return end
if not aZone.smokeColor then return end if not aZone.smokeColor then return end
-- remove old smoke if running
if cfxSmokeZone.verbose or aZone.verbose then trigger.action.outText("+++smk: starting zone <" .. aZone.name .. "> smoke with name <" .. aZone.smokeName .. ">", 30) end
trigger.action.effectSmokeStop(aZone.smokeName)
aZone.paused = false aZone.paused = false
cfxZones.markZoneWithSmoke(aZone, 0, 0, aZone.smokeColor, aZone.smokeAlt) aZone:markZoneWithSmoke(0, 0, aZone.smokeColor, aZone.smokeAlt, aZone.smokeName)
end end
function cfxSmokeZone.removeSmokeZone(aZone) function cfxSmokeZone.stopSmoke(aZone)
if type(aZone) == "string" then
aZone = cfxZones.getZoneByName(aZone)
end
if not aZone then return end if not aZone then return end
if cfxSmokeZone.verbose or aZone.verbose then trigger.action.outText("+++smk: ENDING zone <" .. aZone.name .. ">'s smoke with name <" .. aZone.smokeName .. ">", 30) end
-- now create new table trigger.action.effectSmokeStop(aZone.smokeName)
aZone.paused = true
end
function cfxSmokeZone.removeSmokeZone(aZone)
if not aZone then return end
local filtered = {} local filtered = {}
for idx, theZone in pairs(cfxSmokeZone.smokeZones) do for idx, theZone in pairs(cfxSmokeZone.smokeZones) do
if theZone ~= aZone then if theZone ~= aZone then
@ -112,12 +78,9 @@ function cfxSmokeZone.removeSmokeZone(aZone)
cfxSmokeZone.smokeZones = filtered cfxSmokeZone.smokeZones = filtered
end end
function cfxSmokeZone.update() function cfxSmokeZone.update()
-- call me in a couple of minutes to 'rekindle' -- 'rekindle' all smoke after 5 mins
timer.scheduleFunction(cfxSmokeZone.update, {}, timer.getTime() + cfxSmokeZone.updateDelay) timer.scheduleFunction(cfxSmokeZone.update, {}, timer.getTime() + cfxSmokeZone.updateDelay)
-- re-smoke all zones after delay
for idx, aZone in pairs(cfxSmokeZone.smokeZones) do for idx, aZone in pairs(cfxSmokeZone.smokeZones) do
if not aZone.paused and aZone.smokeColor then if not aZone.paused and aZone.smokeColor then
cfxSmokeZone.startSmoke(aZone) cfxSmokeZone.startSmoke(aZone)
@ -125,52 +88,72 @@ function cfxSmokeZone.update()
end end
end end
function cfxSmokeZone.checkFlags() function cfxSmokeZone.checkFlags()
timer.scheduleFunction(cfxSmokeZone.checkFlags, {}, timer.getTime() + 1) -- every second timer.scheduleFunction(cfxSmokeZone.checkFlags, {}, timer.getTime() + 1) -- every second
for idx, aZone in pairs(cfxSmokeZone.smokeZones) do for idx, aZone in pairs(cfxSmokeZone.smokeZones) do
if aZone.paused and aZone.onFlagVal then if aZone.paused and aZone.onFlagVal then
-- see if this changed -- see if this changed
if cfxZones.testZoneFlag(aZone, aZone.onFlag, aZone.smokeTriggerMethod, "onFlagVal") then if cfxZones.testZoneFlag(aZone, aZone.onFlag, aZone.smokeTriggerMethod, "onFlagVal") then
cfxSmokeZone.startSmoke(aZone) cfxSmokeZone.startSmoke(aZone)
end end
end end
if aZone.smkStopFlag then if aZone.smkStopFlag then
if cfxZones.testZoneFlag(aZone, aZone.smkStopFlag, aZone.smokeTriggerMethod, "smkLastStopFlag") then if cfxZones.testZoneFlag(aZone, aZone.smkStopFlag, aZone.smokeTriggerMethod, "smkLastStopFlag") then
aZone.paused = true -- will no longer re-smoke on update cfxSmokeZone.stopSmoke(aZone)
end end
end end
end end
end end
function cfxSmokeZone.start() function cfxSmokeZone.saveData()
if not dcsCommon.libCheck("cfx Smoke Zones", cfxSmokeZone.requiredLibs) then local theData = {}
return false for idx, theZone in pairs(cfxSmokeZone.smokeZones) do
local entry = {}
entry.paused = theZone.paused
theData[theZone.name] = entry
end
-- save current log. simple clone
return theData, cfxSmokeZone.sharedData -- second val only if shared
end
function cfxSmokeZone.loadData()
if not persistence then return end
local theData = persistence.getSavedDataForModule("smokeZones", cfxSmokeZone.sharedData)
if not theData then
if cfxSmokeZone.verbose then trigger.action.outText("+++smk: no save date received, skipping.", 30) end
return
end end
for name, entry in pairs(theData) do
-- collect all zones with 'smoke' attribute local theZone = cfxSmokeZone.getSmokeZoneNamed(name)
if theZone then
theZone.paused = entry.paused
end
end
end
function cfxSmokeZone.start()
if not dcsCommon.libCheck("cfx Smoke Zones", cfxSmokeZone.requiredLibs) then return false end
local attrZones = cfxZones.getZonesWithAttributeNamed("smoke") local attrZones = cfxZones.getZonesWithAttributeNamed("smoke")
for k, aZone in pairs(attrZones) do for k, aZone in pairs(attrZones) do
cfxSmokeZone.processSmokeZone(aZone) cfxSmokeZone.processSmokeZone(aZone)
cfxSmokeZone.addSmokeZone(aZone) cfxSmokeZone.addSmokeZone(aZone)
end
if persistence then -- sign up for persistence
callbacks = {}
callbacks.persistData = cfxSmokeZone.saveData
persistence.registerModule("smokeZones", callbacks)
-- now load my data
persistence.loadData() -- will start with update
end end
-- start update and checkflag loops
-- start update loop
cfxSmokeZone.update() -- also starts all unpaused cfxSmokeZone.update() -- also starts all unpaused
-- start check loop in one second
timer.scheduleFunction(cfxSmokeZone.checkFlags, {}, timer.getTime() + 1) timer.scheduleFunction(cfxSmokeZone.checkFlags, {}, timer.getTime() + 1)
-- say hi
trigger.action.outText("cfx smoke zones v" .. cfxSmokeZone.version .. " started.", 30) trigger.action.outText("cfx smoke zones v" .. cfxSmokeZone.version .. " started.", 30)
return true return true
end end
-- let's go -- let's go
if not cfxSmokeZone.start() then if not cfxSmokeZone.start() then
trigger.action.outText("cf/x Smoke Zones aborted: missing libraries", 30) trigger.action.outText("cf/x Smoke Zones failed to start", 30)
cfxSmokeZone = nil cfxSmokeZone = nil
end end