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.version = "2.3.0"
FARPZones.version = "2.4.0"
FARPZones.verbose = false
--[[--
Version History
@ -28,6 +28,7 @@ FARPZones.verbose = false
2.2.0 - changing a FARP's owner invokes SSBClient if it is loaded
2.3.0 - new attributes redCap!, blueCap! captured! and farpMethod
- 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
if farps then
local delay = timer.getTime() + 0.2
for fName, fData in pairs(farps) do
local theFARP = FARPZones.getFARPZoneByName(fName)
if theFARP then
@ -574,8 +576,10 @@ function FARPZones.loadMission()
theFARP.defenderData = dcsCommon.clone(fData.defenderData)
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
trigger.action.outText("frpZ: persistence: FARP <" .. fName .. "> no longer exists in mission, skipping", 30)
end

View File

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

View File

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

View File

@ -1,5 +1,5 @@
cloneZones = {}
cloneZones.version = "2.5.2"
cloneZones.version = "2.6.0"
cloneZones.verbose = false
cloneZones.requiredLibs = {
"dcsCommon", -- always
@ -61,6 +61,8 @@ cloneZones.respawnOnGroupID = true
empty! detection through saves (missed hasClones)
2.5.1 - f? and in? put on notice for depreciation
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
theZone.health = theZone:getStringFromZoneProperty("health#")
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
theZone.lastSize = 0 -- no units here
end
@ -1506,6 +1516,18 @@ function cloneZones.spawnWithCloner(theZone)
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
local templateZone = theZone
if theZone.source then
@ -1903,6 +1925,9 @@ function cloneZones.saveData()
local cData = {}
local cName = theCloner.name
cData.myUniqueCounter = theCloner.myUniqueCounter
if theCloner.maxCycles then
cData.maxCycles = theCloner.maxCycles
end
cData.oSize = theCloner.oSize
cData.lastSize = theCloner.lastSize
-- mySpawns: all groups I'm curently observing for empty!
@ -1946,7 +1971,8 @@ function cloneZones.loadData()
end
return
end
local gSpawn = 0
local uSpawn = 0
-- spawn all units
local allClones = theData.clones
for gName, gData in pairs (allClones) do
@ -1958,12 +1984,15 @@ function cloneZones.loadData()
local gdClone = dcsCommon.clone(gData)
cloneZones.allClones[gName] = gdClone
local theGroup = coalition.addGroup(cty, cat, gData)
uSpawn = uSpawn + #theGroup:getUnits()
gSpawn = gSpawn + 1
-- turn off AI if disabled
if not gData.useAI then
cloneZones.turnOffAI({theGroup})
end
end
if cloneZones.verbose then trigger.action.outText("load: loaded <" .. gSpawn .. "> groups, <" .. uSpawn .. "> units total.", 30) end
-- spawn all static objects
local allObjects = theData.objects
for oName, oData in pairs(allObjects) do
@ -2001,6 +2030,7 @@ function cloneZones.loadData()
end
if cData.oSize then theCloner.oSize = cData.oSize end
if cData.lastSize then theCloner.lastSize = cData.lastSize end
if cData.maxCycles then theCloner.maxCycles = cData.maxCycles end
local mySpawns = {}
for idx, aName in pairs(cData.mySpawns) do

View File

@ -1,56 +1,16 @@
csarManager = {}
csarManager.version = "4.2.1"
csarManager.version = "4.3.0"
csarManager.ups = 1
--[[-- 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.1 - increased verbosity
- fix for Jul-11 2024 DCS bugs
4.1.0 - support for DCS 2.9.6 dynamic spawns
4.2.0 - automatically support twn if present
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 WITH LIMITED AIRFRAMES
@ -549,6 +509,14 @@ function csarManager.heloLanded(theUnit)
30)
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 = {}
args.theName = theMission.name
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
end
-- also pop smoke if not popped already, or more than 5 minutes ago
if csarManager.useSmoke and (timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then
-- pop smoke if not popped already, or more than 5 minutes ago
if csarManager.useSmoke and
(timer.getTime() - csarMission.lastSmokeTime) >= 5 * 60 then
if csarMission.lastSmokeTime < 0 then
-- this is the first time that this mission pops smoke
-- trigger.action.outText("***will invoke smoke cb", 30)
csarManager.invokeSmokeCallbacks(csarMission, uName)
else
--trigger.action.outText("nope smoke's a dope", 30)
end
local smokePoint = dcsCommon.randomPointOnPerimeter(
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()
end
@ -1236,6 +1205,13 @@ function csarManager.update() -- every second
table.insert(conf.troopsOnBoard, csarMission)
csarMission.group:destroy() -- will shut up radio as well
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
-- now handle weight using cargoSuper
@ -1425,8 +1401,7 @@ function csarManager.readCSARZone(theZone)
theZone.numCrew = 1
theZone.csarMapMarker = nil
if theZone:hasProperty("timeLimit") then
local tmin, tmax = theZone:getPositiveRangeFromZoneProperty("timeLimit", 1)
local tmin, tmax = theZone:getPositiveRangeFromZoneProperty("timeLimit", 1) -- minutes
theZone.timeLimit = {tmin, tmax}
else
theZone.timeLimit = nil
@ -1572,6 +1547,7 @@ function csarManager.readConfigZone()
csarManager.ups = theZone:getNumberFromZoneProperty("ups", 1)
csarManager.useSmoke = theZone:getBoolFromZoneProperty("useSmoke", true)
csarManager.keepSmoke = theZone:getBoolFromZoneProperty("keepSmoke", false)
csarManager.smokeColor = theZone:getSmokeColorStringFromZoneProperty("smokeColor", "blue")
csarManager.smokeDist = theZone:getNumberFromZoneProperty("smokeDist", 30)
csarManager.smokeColor = dcsCommon.smokeColor2Num(csarManager.smokeColor)
@ -1807,4 +1783,5 @@ end
player that they did not survive the transport
- randomize smoke color if smoke color has more than one entries
- ELT freq higher bands?
--]]--

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "3.2.0"
dcsCommon.version = "3.2.1"
--[[-- VERSION HISTORY
3.0.0 - removed bad bug in stringStartsWith, only relevant if caseSensitive is false
- point2text new intsOnly option
@ -39,6 +39,7 @@ dcsCommon.version = "3.2.0"
- cfxZones links to processTimeLocWildCards
- new processAtoBWildCards()
- tons of new wildcards like <eleft>
3.2.1 - markPointWithSmoke supports names and returns names
--]]--
-- dcsCommon is a library of common lua functions
@ -2650,14 +2651,16 @@ end
end
function dcsCommon.markPointWithSmoke(p, smokeColor)
function dcsCommon.markPointWithSmoke(p, smokeColor, name)
if not smokeColor then smokeColor = 0 end
if not name then name = dcsCommon.uuid("dcsCsmoke") end
local x = p.x
local z = p.z -- do NOT change the point directly
-- height-correct
local y = land.getHeight({x = x, y = z})
local newPoint= {x = x, y = y + 2, z = z}
trigger.action.smoke(newPoint, smokeColor)
trigger.action.smoke(newPoint, smokeColor, name)
return name
end
-- 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.version = "4.2.2"
cfxHeloTroops.version = "4.2.3"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -22,6 +22,8 @@ cfxHeloTroops.requestRange = 500 -- meters
4.2.1 - increased verbosity
- also supports 'pickupRang" for reverse-compatibility with manual typo.
4.2.2 - support for attachTo:
4.2.3 - dropZone supports 'keepWait' attribute
- dropZone supports 'setWait' attribute
--]]--
cfxHeloTroops.minTime = 3 -- seconds beween tandings
@ -48,6 +50,8 @@ function cfxHeloTroops.processDropZone(theZone)
theZone.dropMethod = theZone:getStringFromZoneProperty("dropMethod", "inc")
theZone.dropCoa = theZone:getCoalitionFromZoneProperty("coalition", 0)
theZone.autoDespawn = theZone:getNumberFromZoneProperty("autoDespawn", -1)
theZone.keepWait = theZone:getBoolFromZoneProperty("keepWait", false)
theZone.setWait = theZone:getBoolFromZoneProperty("setWait", false)
end
--
@ -704,20 +708,47 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf)
local moveFormation = conf.troopsOnBoard.moveFormation
local code = conf.troopsOnBoard.code
local canDrive = conf.troopsOnBoard.canDrive
local theCoalition = theUnit:getGroup():getCoalition() -- my coa
if not orders then orders = "guard" end
orders = string.lower(orders)
-- order processing: if the orders were pre-pended with "wait-"
-- we now remove that, so after dropping they do what their
-- orders where AFTER being picked up
-- order "wait" processing if not in special drop zone:
-- if the orders were pre-pended with "wait-"
-- 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
orders = dcsCommon.removePrefix(orders, "wait-")
trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> revoke 'wait' orders, proceed with <".. orders .. ">", 30)
local dropWait = false
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
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 (
theCoalition,
theName, -- group name, may be tracked
@ -1173,4 +1204,5 @@ if not cfxHeloTroops.start() then
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.version = "2.4.1"
cfxOwnedZones.version = "2.5.0"
cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true
cfxOwnedZones.name = "cfxOwnedZones"
@ -45,6 +45,8 @@ cfxOwnedZones.name = "cfxOwnedZones"
2.3.1 - restored getNearestOwnedZoneToPoint
2.4.0 - dmlZones masterOwner update
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 = {
@ -125,11 +127,11 @@ function cfxOwnedZones.drawZoneInMap(aZone)
end
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
if aZone.hidden then return end
aZone.markID = aZone:drawZone(lineColor, fillColor) -- markID
aZone.markID = aZone:drawZone(lineColor, fillColor, nil) -- markID
end
function cfxOwnedZones.getOwnedZoneByName(zName)
@ -748,7 +750,7 @@ function cfxOwnedZones.loadData()
local theData = persistence.getSavedDataForModule("cfxOwnedZones")
if not theData 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
return
end
@ -757,6 +759,9 @@ function cfxOwnedZones.loadData()
-- flagInfo: module-global flags
-- attackers: all spawned attackers that we feed to groundTroops
local allZoneData = theData.zoneData
local now = timer.getTime()
local delay = now + 0.5
for zName, zData in pairs(allZoneData) do
-- access zone
local theZone = cfxOwnedZones.getOwnedZoneByName(zName)
@ -766,7 +771,10 @@ function cfxOwnedZones.loadData()
theZone:setFlagValue(theZone.conqueredFlag, zData.conquered)
end
-- 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
trigger.action.outText("owdZ: load - data mismatch: cannot find zone <" .. zName .. ">, skipping zone.", 30)
end

View File

@ -1,5 +1,5 @@
persistence = {}
persistence.version = "3.0.2"
persistence.version = "3.1.0"
persistence.ups = 1 -- once every 1 seconds
persistence.verbose = false
persistence.active = false
@ -24,6 +24,8 @@ persistence.requiredLibs = {
code cleanup
3.0.2 - more logging
vardump to log possible
3.1.0 - validFor attribute -- timed gc
- persistence deallocs data after validFor seconds
PROVIDES LOAD/SAVE ABILITY TO MODULES
PROVIDES STANDALONE/HOSTED SERVER COMPATIBILITY
@ -74,12 +76,43 @@ end
--
-- 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)
-- 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.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 persistence.filter(name) then
trigger.action.outText("FILTERED persistence for <" .. name .. ">", 30)
return nil
end
if sharedDataName then
-- we read from shared data and only revert to
-- common if we find nothing
@ -90,6 +123,7 @@ function persistence.getSavedDataForModule(name, sharedDataName)
local theData = persistence.loadTable(shFile, true)
if theData then
if theData[name] then
trigger.action.outText("+++persistence: returning SHARED for <" .. name .. ">", 30)
return theData[name]
end
if persistence.verbose then
@ -102,6 +136,9 @@ function persistence.getSavedDataForModule(name, sharedDataName)
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
end
@ -580,12 +617,25 @@ function persistence.readConfigZone()
persistence.saveNotification = theZone:getBoolFromZoneProperty("saveNotification", true)
persistence.validFor = theZone:getNumberFromZoneProperty("validFor", 5) -- GC after ... seconds
if persistence.verbose then
trigger.action.outText("+++persistence: read config", 30)
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()
-- lib check
if not dcsCommon.libCheck then
@ -720,7 +770,7 @@ function persistence.start()
-- we now see if we can and need load data
persistence.missionStartDataLoad()
timer.scheduleFunction(persistence.GC, nil, timer.getTime() + persistence.validFor) -- destroy loaded data after this interval
-- and start updating
persistence.update()

View File

@ -1,5 +1,5 @@
pulseFlags = {}
pulseFlags.version = "2.0.3"
pulseFlags.version = "2.0.4"
pulseFlags.verbose = false
pulseFlags.requiredLibs = {
"dcsCommon", -- always
@ -8,7 +8,7 @@ pulseFlags.requiredLibs = {
--[[--
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
- 2.0.0 dmlZones / OOP
@ -16,6 +16,7 @@ pulseFlags.requiredLibs = {
- 2.0.1 activateZoneFlag now works correctly
- 2.0.2 fixed scheduledTime bug while persisting
- 2.0.3 now setting -1 (infinite) as pulses works correctly
- 2.0.4 corrected typo when checking verbosity
--]]--
pulseFlags.pulses = {}
@ -69,7 +70,7 @@ function pulseFlags.createPulseWithZone(theZone)
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)
end

View File

@ -1,5 +1,5 @@
reaper = {}
reaper.version = "1.2.0"
reaper.version = "1.3.0"
reaper.requiredLibs = {
"dcsCommon",
"cfxZones",
@ -21,8 +21,9 @@ VERSION HISTORY
- completely rewrote scanning method (performance)
- added FAC task
- 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.3.0 - new invisible option for drone zone
--]]--
@ -78,6 +79,7 @@ function reaper.readReaperZone(theZone)
theZone.cycle = theZone:getStringFromZoneProperty("cycle?", "<none>")
theZone.cycleVal = theZone:getFlagValue(theZone.cycle)
end
theZone.invisible = theZone:getBoolFromZoneProperty("invisible", false)
theZone.hasSpawned = false
@ -149,7 +151,7 @@ function reaper.spawnForZone(theZone, ack)
-- now create and add waypoints to route
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
local wp2 = dcsCommon.createSimpleRoutePointData(right, unit.alt, unit.speed)
gdata.route.points[2] = wp2
@ -195,75 +197,89 @@ function reaper.cleanUp(theZone)
theZone.theSpot = nil
end
function reaper.createReaperTask(alt, speed, target, theZone)
local task = {
function reaper.createReaperTask(alt, speed, target, theZone, invisible)
if not invisible then invisible = false end
local task = {
["id"] = "ComboTask",
["params"] = {
["tasks"] = {
[1] = {
["enabled"] = true,
["auto"] = true,
["id"] = "FAC",
["number"] = 1,
["params"] =
{}, -- end of ["params"]
["auto"] = false,
["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]
[2] = {
["enabled"] = true,
["auto"] = true,
["id"] = "WrappedAction",
["id"] = "FAC",
["number"] = 2,
["params"] =
{}, -- end of ["params"]
}, -- end of [2]
[3] = {
["enabled"] = true,
["auto"] = true,
["id"] = "WrappedAction",
["number"] = 3,
["params"] = {
["action"] = {
["id"] = "EPLRS",
["params"] = {
["value"] = true,
["groupId"] = 1,
["groupId"] = 1, -- <- looks bad
}, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [2]
[3] = {
}, -- end of [3]
[4] = {
["enabled"] = true,
["auto"] = false,
["id"] = "Orbit",
["number"] = 3,
["number"] = 4,
["params"] = {
["altitude"] = alt,
["pattern"] = "Race-Track",
["speed"] = speed,
}, -- end of ["params"]
}, -- end of [3]
}, -- end of [4]
}, -- end of ["tasks"]
}, -- end of ["params"]
} -- end of ["task"]
if theTarget and theZone then
-- local gID = theTarget:getGroup():getID()
local gID = theTarget:getID() -- NOTE: theTarget is a GROUP!!!!
local task4 = {
local task4 = { -- now task5 after we added invisibility
["enabled"] = true,
["auto"] = false,
["id"] = "FAC_AttackGroup",
["number"] = 4,
["number"] = 5,
["params"] =
{
["number"] = 1,
["number"] = 5,
["designation"] = "No",
["modulation"] = 0,
["groupId"] = gID,
-- ["callname"] = 1,
-- ["datalink"] = true,
["weaponType"] = 0, -- 9663676414,
["frequency"] = theZone.freq, -- 133000000,
}, -- end of ["params"]
} -- end of [4]
task.params.tasks[4] = task4
} -- end of [5]
task.params.tasks[5] = task4
end
return task
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 = {
["alt"] = alt,
["action"] = "Turning Point",
@ -282,7 +298,7 @@ function reaper.createInitialWP(p, alt, speed) -- warning: target must be a GROU
["formation_template"] = "",
} -- 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
end
@ -319,7 +335,7 @@ function reaper.setTarget(theZone, theTarget, cycled)
-- now make tracking the group the drone's task
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()
if not theController then
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
todo: make reaper invisible by attribute
--]]--

View File

@ -1,108 +1,74 @@
cfxSmokeZone = {}
cfxSmokeZone.version = "2.0.1"
cfxSmokeZone.version = "3.0.0"
cfxSmokeZone.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
--[[--
Version History
2.0.0 - clean up
2.0.1 - deprecating "f?"
Version History
3.0.0 - now supports immediate smoke stop
- supports persistence
- code cleanup
--]]--
cfxSmokeZone.smokeZones = {}
cfxSmokeZone.updateDelay = 5 * 60 -- every 5 minutes
function cfxSmokeZone.processSmokeZone(aZone)
local rawVal = aZone:getStringFromZoneProperty("smoke", "green")
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.smokeColor = aZone:getSmokeColorNumberFromZoneProperty("smoke", "green")--theColor
aZone.smokeAlt = aZone:getNumberFromZoneProperty("altitude", 1)
aZone.smokeName = aZone.name .. "-s-" .. dcsCommon.numberUUID()
if aZone:hasProperty("alt") then
aZone.smokeAlt = aZone:getNumberFromZoneProperty("alt", 1)
elseif aZone:hasProperty("agl") then
aZone.smokeAlt = aZone:getNumberFromZoneProperty("agl", 1)
end
-- paused
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
aZone.paused = aZone:getBoolFromZoneProperty("paused", false)
aZone.onFlag = aZone:getStringFromZoneProperty("startSmoke?", "none")
if aZone.onFlag then
aZone.onFlagVal = aZone:getFlagValue(aZone.onFlag) -- save last value
end
if aZone:hasProperty("stopSmoke?") then
aZone.smkStopFlag = aZone:getStringFromZoneProperty("stopSmoke?", "<none>")
aZone.smkLastStopFlag = aZone:getFlagValue(aZone.smkStopFlag)
end
aZone.smokeTriggerMethod = aZone:getStringFromZoneProperty( "triggerMethod", "change")
if aZone:hasProperty("smokeTriggerMethod") then
aZone.smokeTriggerMethod = aZone:getStringFromZoneProperty( "smokeTriggerMethod", "change")
end
end
function cfxSmokeZone.addSmokeZone(aZone)
table.insert(cfxSmokeZone.smokeZones, aZone)
end
function cfxSmokeZone.addSmokeZoneWithColor(aZone, aColor, anAltitude, paused, onFlag)
if not aColor then aColor = 0 end -- default green
if not anAltitude then anAltitude = 5 end
if not aZone then return end
if not paused then paused = false end
aZone.smokeColor = aColor
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
function cfxSmokeZone.getSmokeZoneNamed(aName)
if not aName then return end
local aName = string.upper(aName)
for idx, theZone in pairs(cfxSmokeZone.smokeZones) do
if theZone.name == aName then return theZone end
end
return nil
end
function cfxSmokeZone.startSmoke(aZone)
if type(aZone) == "string" then
aZone = cfxZones.getZoneByName(aZone)
end
if not aZone 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
cfxZones.markZoneWithSmoke(aZone, 0, 0, aZone.smokeColor, aZone.smokeAlt)
aZone:markZoneWithSmoke(0, 0, aZone.smokeColor, aZone.smokeAlt, aZone.smokeName)
end
function cfxSmokeZone.removeSmokeZone(aZone)
if type(aZone) == "string" then
aZone = cfxZones.getZoneByName(aZone)
end
function cfxSmokeZone.stopSmoke(aZone)
if not aZone then return end
-- now create new table
if cfxSmokeZone.verbose or aZone.verbose then trigger.action.outText("+++smk: ENDING zone <" .. aZone.name .. ">'s smoke with name <" .. aZone.smokeName .. ">", 30) end
trigger.action.effectSmokeStop(aZone.smokeName)
aZone.paused = true
end
function cfxSmokeZone.removeSmokeZone(aZone)
if not aZone then return end
local filtered = {}
for idx, theZone in pairs(cfxSmokeZone.smokeZones) do
if theZone ~= aZone then
@ -112,12 +78,9 @@ function cfxSmokeZone.removeSmokeZone(aZone)
cfxSmokeZone.smokeZones = filtered
end
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)
-- re-smoke all zones after delay
for idx, aZone in pairs(cfxSmokeZone.smokeZones) do
if not aZone.paused and aZone.smokeColor then
cfxSmokeZone.startSmoke(aZone)
@ -125,52 +88,72 @@ function cfxSmokeZone.update()
end
end
function cfxSmokeZone.checkFlags()
timer.scheduleFunction(cfxSmokeZone.checkFlags, {}, timer.getTime() + 1) -- every second
for idx, aZone in pairs(cfxSmokeZone.smokeZones) do
if aZone.paused and aZone.onFlagVal then
-- see if this changed
if cfxZones.testZoneFlag(aZone, aZone.onFlag, aZone.smokeTriggerMethod, "onFlagVal") then
cfxSmokeZone.startSmoke(aZone)
end
end
if aZone.smkStopFlag 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
function cfxSmokeZone.start()
if not dcsCommon.libCheck("cfx Smoke Zones", cfxSmokeZone.requiredLibs) then
return false
function cfxSmokeZone.saveData()
local theData = {}
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
-- collect all zones with 'smoke' attribute
for name, entry in pairs(theData) do
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")
for k, aZone in pairs(attrZones) do
cfxSmokeZone.processSmokeZone(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
-- start update loop
-- start update and checkflag loops
cfxSmokeZone.update() -- also starts all unpaused
-- start check loop in one second
timer.scheduleFunction(cfxSmokeZone.checkFlags, {}, timer.getTime() + 1)
-- say hi
trigger.action.outText("cfx smoke zones v" .. cfxSmokeZone.version .. " started.", 30)
return true
end
-- let's go
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
end