Version 0.97

Clone Zones
MX
delayFlags
Attack of the CloneZ miz
This commit is contained in:
Christian Franz 2022-02-03 18:37:20 +01:00
parent 9162ea3acf
commit 5ae7f36482
10 changed files with 844 additions and 24 deletions

Binary file not shown.

View File

@ -278,7 +278,9 @@ end
function rndFlags.startCycle() function rndFlags.startCycle()
for idx, theZone in pairs(rndFlags.rndGen) do for idx, theZone in pairs(rndFlags.rndGen) do
if theZone.onStart then if theZone.onStart then
trigger.action.outText("+++RND: starting " .. theZone.name, 30) if rndFlags.verbose then
trigger.action.outText("+++RND: starting " .. theZone.name, 30)
end
rndFlags.fire(theZone) rndFlags.fire(theZone)
end end
end end

81
modules/cfxMX.lua Normal file
View File

@ -0,0 +1,81 @@
cfxMX = {}
cfxMX.version = "1.0.0"
--[[--
Mission data decoder. Access to ME-built mission structures
Copyright (c) 2022 by Christian Franz and cf/x AG
Version History
1.0.0 - initial version
--]]--
function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal)
if not fetchOriginal then fetchOriginal = false end
-- fetch the group description for goup named aName (if exists)
-- returned structure must be parsed for useful information
-- returns data, category, countyID and coalitionID
-- unless fetchOriginal is true, creates a deep clone of
-- group data structure
for coa_name_miz, coa_data in pairs(env.mission.coalition) do -- iterate all coalitions
local coa_name = coa_name_miz
if string.lower(coa_name_miz) == 'neutrals' then -- remove 's' at neutralS
coa_name = 'neutral'
end
-- directly convert coalition into number for easier access later
local coaNum = 0
if coa_name == "red" then coaNum = 1 end
if coa_name == "blue" then coaNum = 2 end
if type(coa_data) == 'table' then -- coalition = {bullseye, nav_points, name, county},
-- with county being an array
if coa_data.country then -- make sure there a country table for this coalition
for cntry_id, cntry_data in pairs(coa_data.country) do -- iterate all countries for this
-- per country = {id, name, vehicle, helicopter, plane, ship, static}
local countryName = string.lower(cntry_data.name)
local countryID = cntry_data.id
if type(cntry_data) == 'table' then -- filter strings .id and .name
for obj_type_name, obj_type_data in pairs(cntry_data) do
if obj_type_name == "helicopter" or
obj_type_name == "ship" or
obj_type_name == "plane" or
obj_type_name == "vehicle" or
obj_type_name == "static"
then -- (so it's not id or name)
local category = obj_type_name
if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's at least one group!
for group_num, group_data in pairs(obj_type_data.group) do
if group_data.name == aName then
local theGroup = group_data
-- usually we return a copy of this
if not fetchOriginal then
theGroup = dcsCommon.clone(group_data)
end
return theGroup, category, countryID
end
end
end --if has category data
end --if plane, helo etc... category
end --for all objects in country
end --if has country data
end --for all countries in coalition
end --if coalition has country table
end -- if there is coalition data
end --for all coalitions in mission
return nil, "none", "none"
end
function cfxMX.catText2ID(inText)
local outCat = 0 -- airplane
local c = inText:lower()
if c == "helicopter" then outCat = 1 end
if c == "ship" then outCat = 3 end
if c == "plane" then outCat = 0 end -- redundant
if c == "vehicle" then outCat = 2 end
if c == "train" then outCat = 4 end
if c == "static" then outCat = -1 end
return outCat
end

View File

@ -1,5 +1,5 @@
cfxOwnedZones = {} cfxOwnedZones = {}
cfxOwnedZones.version = "1.1.0" cfxOwnedZones.version = "1.1.1"
cfxOwnedZones.verbose = false cfxOwnedZones.verbose = false
cfxOwnedZones.announcer = true cfxOwnedZones.announcer = true
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
@ -39,6 +39,7 @@ cfxOwnedZones.announcer = true
- remove exiting defenders from zone after cap to avoid - remove exiting defenders from zone after cap to avoid
shocked state shocked state
- announcer - announcer
1.1.1 - conq+1 flag
--]]-- --]]--
cfxOwnedZones.requiredLibs = { cfxOwnedZones.requiredLibs = {
@ -201,6 +202,10 @@ function cfxOwnedZones.addOwnedZone(aZone)
local paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false) local paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false)
aZone.paused = paused aZone.paused = paused
if cfxZones.hasProperty(aZone, "conq+1") then
cfxOwnedZones.conqueredFlag = cfxZones.getNumberFromZoneProperty(theZone, "conq+1", -1)
end
aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false) aZone.unbeatable = cfxZones.getBoolFromZoneProperty(aZone, "unbeatable", false)
aZone.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", false) aZone.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", false)
aZone.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden", false) aZone.hidden = cfxZones.getBoolFromZoneProperty(aZone, "hidden", false)
@ -489,6 +494,11 @@ function cfxOwnedZones.zoneConquered(aZone, theSide, formerOwner) -- 0 = neutral
trigger.action.outSoundForCoalition(1, "Death BRASS.wav") trigger.action.outSoundForCoalition(1, "Death BRASS.wav")
end end
end end
-- increase conq flag
if aZone.conqueredFlag then
local lastVal = trigger.misc.getUserFlag(aZone.conqueredFlag)
trigger.action.setUserFlag)aZone.conqueredFlag, lastVal + 1)
end
-- invoke callbacks now -- invoke callbacks now
cfxOwnedZones.invokeConqueredCallbacks(aZone, theSide, formerOwner) cfxOwnedZones.invokeConqueredCallbacks(aZone, theSide, formerOwner)

View File

@ -6,7 +6,7 @@
-- --
cfxZones = {} cfxZones = {}
cfxZones.version = "2.5.2" cfxZones.version = "2.5.3"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
- 2.2.4 - getCoalitionFromZoneProperty - 2.2.4 - getCoalitionFromZoneProperty
- getStringFromZoneProperty - getStringFromZoneProperty
@ -45,6 +45,9 @@ cfxZones.version = "2.5.2"
- 2.5.1 - markZoneWithSmoke supports alt attribute - 2.5.1 - markZoneWithSmoke supports alt attribute
- 2.5.2 - getPoint also writes through to zone itself for optimization - 2.5.2 - getPoint also writes through to zone itself for optimization
- new method getPositiveRangeFromZoneProperty(theZone, theProperty, default) - new method getPositiveRangeFromZoneProperty(theZone, theProperty, default)
- 2.5.3 - new getAllGroupsInZone()
- 2.5.4 - cleaned up getZoneProperty break on no properties
- extractPropertyFromDCS trims key and property
--]]-- --]]--
cfxZones.verbose = true cfxZones.verbose = true
@ -543,19 +546,28 @@ end
-- --
-- units / groups in zone -- units / groups in zone
-- --
function cfxZones.allGroupsInZone(theZone, categ) -- categ is optional, must be code
-- warning: does not check for exiting!
local inZones = {}
local coals = {0, 1, 2} -- all coalitions
for idx, coa in pairs(coals) do
local allGroups = coalition.getGroups(coa, categ)
for key, group in pairs(allGroups) do -- iterate all groups
if cfxZones.isGroupPartiallyInZone(group, theZone) then
table.insert(inZones, group)
end
end
end
return inZones
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)
for key, group in pairs(allGroups) do -- iterate all groups for key, group in pairs(allGroups) do -- iterate all groups
if group:isExist() then if group:isExist() then
if cfxZones.isGroupPartiallyInZone(group, theZone) then if cfxZones.isGroupPartiallyInZone(group, theZone) then
table.insert(groupsInZone, group) table.insert(groupsInZone, group)
else
end end
end end
end end
@ -566,20 +578,13 @@ function cfxZones.isGroupPartiallyInZone(aGroup, aZone)
if not aGroup then return false end if not aGroup then return false end
if not aZone then return false end if not aZone then return false end
-- needs to be implemented
if not aGroup:isExist() then return false end if not aGroup:isExist() then return false end
local allUnits = aGroup:getUnits() local allUnits = aGroup:getUnits()
for uk, aUnit in pairs (allUnits) do for uk, aUnit in pairs (allUnits) do
if aUnit:isExist() and aUnit:getLife() > 1 then if aUnit:isExist() and aUnit:getLife() > 1 then
local p = aUnit:getPoint() local p = aUnit:getPoint()
-- p.y = 0 -- zones have no altitude
-- modification of isPointInsideZone now takes care of this
if cfxZones.isPointInsideZone(p, aZone) then if cfxZones.isPointInsideZone(p, aZone) then
return true return true
else
end end
end end
end end
@ -589,7 +594,6 @@ end
function cfxZones.isEntireGroupInZone(aGroup, aZone) function cfxZones.isEntireGroupInZone(aGroup, aZone)
if not aGroup then return false end if not aGroup then return false end
if not aZone then return false end if not aZone then return false end
-- needs to be implemented
if not aGroup:isExist() then return false end if not aGroup:isExist() then return false end
local allUnits = aGroup:getUnits() local allUnits = aGroup:getUnits()
for uk, aUnit in pairs (allUnits) do for uk, aUnit in pairs (allUnits) do
@ -1047,6 +1051,8 @@ function cfxZones.getAllZoneProperties(theZone, caseInsensitive) -- return as di
end end
function cfxZones.extractPropertyFromDCS(theKey, theProperties) function cfxZones.extractPropertyFromDCS(theKey, theProperties)
-- trim
theKey = dcsCommon.trim(theKey)
-- make lower case conversion if not case sensitive -- make lower case conversion if not case sensitive
if not cfxZones.caseSensitiveProperties then if not cfxZones.caseSensitiveProperties then
theKey = string.lower(theKey) theKey = string.lower(theKey)
@ -1055,7 +1061,8 @@ function cfxZones.extractPropertyFromDCS(theKey, theProperties)
-- iterate all keys and compare to what we are looking for -- iterate all keys and compare to what we are looking for
for i=1, #theProperties do for i=1, #theProperties do
local theP = theProperties[i] local theP = theProperties[i]
local existingKey = theP.key
local existingKey = dcsCommon.trim(theP.key)
if not cfxZones.caseSensitiveProperties then if not cfxZones.caseSensitiveProperties then
existingKey = string.lower(existingKey) existingKey = string.lower(existingKey)
end end
@ -1073,7 +1080,7 @@ function cfxZones.getZoneProperty(cZone, theKey)
end end
if not theKey then if not theKey then
trigger.action.outText("+++zone: no property key in getZoneProperty for zone " .. cZone.name, 30) trigger.action.outText("+++zone: no property key in getZoneProperty for zone " .. cZone.name, 30)
breakme.here = 1 -- breakme.here = 1
return return
end end
@ -1349,6 +1356,8 @@ function cfxZones.init()
-- note, all zones with this property are by definition owned zones. -- note, all zones with this property are by definition owned zones.
-- and hence will be read anyway. this will merely ensure that the -- and hence will be read anyway. this will merely ensure that the
-- ownership is established right away -- ownership is established right away
-- unless owned zones module is missing, in which case
-- ownership is still established
local pZones = cfxZones.zonesWithProperty("owner") local pZones = cfxZones.zonesWithProperty("owner")
for n, aZone in pairs(pZones) do for n, aZone in pairs(pZones) do
aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0) aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0)

451
modules/cloneZone.lua Normal file
View File

@ -0,0 +1,451 @@
cloneZones = {}
cloneZones.version = "1.0.0"
cloneZones.verbose = false
cloneZones.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
"cfxMX",
}
cloneZones.cloners = {}
--[[--
Clones Groups from ME mission data
Copyright (c) 2022 by Christian Franz and cf/x AG
Version History
1.0.0 - initial version
--]]--
--
-- adding / removing from list
--
function cloneZones.addCloneZone(theZone)
table.insert(cloneZones.cloners, theZone)
end
function cloneZones.getCloneZoneByName(aName)
for idx, aZone in pairs(cloneZones.cloners) do
if aName == aZone.name then return aZone end
end
if cloneZones.verbose then
trigger.action.outText("+++clnZ: no clone with name <" .. aName ..">", 30)
end
return nil
end
--
-- reading zones
--
function cloneZones.createClonerWithZone(theZone) -- has "Cloner"
local localZones = cfxZones.allGroupsInZone(theZone)
theZone.cloner = true -- this is a cloner zoner
theZone.mySpawns = {}
--theZone.groupVectors = {}
theZone.origin = cfxZones.getPoint(theZone) -- save reference point for all groupVectors
-- source tells us which template to use. it can be the following:
-- nothing (no attribute) - then we use whatever groups are in zone to
-- spawn as template
-- name of another spawner that provides the template
-- we can't simply use a group name as we lack the reference
-- location for delta
if cfxZones.hasProperty(theZone, "source") then
theZone.source = cfxZones.getStringFromZoneProperty(theZone, "source", "<none>")
if theZone.source == "<none>" then theZone.source = nil end
end
if not theZone.source then
theZone.cloneNames = {} -- names of the groups. only present in template spawners
for idx, aGroup in pairs(localZones) do
local gName = aGroup:getName()
if gName then
table.insert(theZone.cloneNames, gName)
table.insert(theZone.mySpawns, aGroup) -- collect them for initial despawn
end
end
cloneZones.despawnAll(theZone)
if #theZone.cloneNames < 1 then
if cloneZones.verbose then
trigger.action.outText("+++clnZ: WARNING - Template in clone zone <" .. theZone.name .. "> is empty", 30)
end
theZone.cloneNames = nil
end
if cloneZones.verbose then
trigger.action.outText(theZone.name .. " clone template saved", 30)
end
end
-- f? and spawn? map to the same
if cfxZones.hasProperty(theZone, "f?") then
theZone.spawnFlag = cfxZones.getStringFromZoneProperty(theZone, "f?", "none")
theZone.lastSpawnValue = trigger.misc.getUserFlag(theZone.spawnFlag) -- save last value
end
if cfxZones.hasProperty(theZone, "spawn?") then
theZone.spawnFlag = cfxZones.getStringFromZoneProperty(theZone, "spawn?", "none")
theZone.lastSpawnValue = trigger.misc.getUserFlag(theZone.spawnFlag) -- save last value
end
theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false)
theZone.moveRoute = cfxZones.getBoolFromZoneProperty(theZone, "moveRoute", false)
if cfxZones.hasProperty(theZone, "empty+1") then
theZone.emptyFlag = cfxZones.getNumberFromZoneProperty(theZone, "empty+1", "<None>") -- note string on number default
end
if cfxZones.hasProperty(theZone, "masterOwner") then
theZone.masterOwner = cfxZones.getStringFromZoneProperty(theZone, "masterOwner", "<none>")
end
--cloneZones.spawnWithCloner(theZone)
theZone.turn = cfxZones.getNumberFromZoneProperty(theZone, "turn", 0)
-- make sure we spawn at least once
-- bad idea, since we may want to simply create a template
-- if not theZone.spawnFlag then theZone.onStart = true end
end
--
-- spawning, despawning
--
function cloneZones.despawnAll(theZone)
for idx, aGroup in pairs(theZone.mySpawns) do
Group.destroy(aGroup)
end
theZone.mySpawns = {}
end
function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWaypoints)
--trigger.action.outText("Update loc - zone delta: [" .. zoneDelta.x .. "," .. zoneDelta.z .. "]", 30)
-- remember that zoneDelta's [z] modifies theData's y!!
theData.x = theData.x + zoneDelta.x
theData.y = theData.y + zoneDelta.z -- !!!
local units = theData.units
for idx, aUnit in pairs(units) do
aUnit.x = aUnit.x + zoneDelta.x
aUnit.y = aUnit.y + zoneDelta.z -- again!!!!
end
-- now modifiy waypoints. we ALWAYS adjust the
-- first waypoint, but only all others if asked
-- to
local theRoute = theData.route
-- TODO: vehicles can have 'spans' - may need to program for
-- those as well. we currently only go for points
if theRoute then
local thePoints = theRoute.points
if thePoints and #thePoints > 0 then
if adjustAllWaypoints then
for i=1, #thePoints do
thePoints[i].x = thePoints[i].x + zoneDelta.x
thePoints[i].y = thePoints[i].y + zoneDelta.z -- (!!)
end
else
-- only first point
thePoints[1].x = thePoints[1].x + zoneDelta.x
thePoints[1].y = thePoints[1].y + zoneDelta.z -- (!!)
end
-- if there is an airodrome id given in first waypoint,
-- adjust for closest location
local firstPoint = thePoints[1]
if firstPoint.airdromeId then
trigger.action.outText("first: airdrome adjust for " .. theData.name .. " now is " .. firstPoint.airdromeId, 30)
local loc = {}
loc.x = firstPoint.x
loc.y = 0
loc.z = firstPoint.y
local bestAirbase = dcsCommon.getClosestAirbaseTo(loc)
firstPoint.airdromeId = bestAirbase:getID()
trigger.action.outText("first: adjusted to " .. firstPoint.airdromeId, 30)
end
-- adjust last point (landing)
if #thePoints > 1 then
local lastPoint = thePoints[#thePoints]
if firstPoint.airdromeId then
trigger.action.outText("last: airdrome adjust for " .. theData.name .. " now is " .. lastPoint.airdromeId, 30)
local loc = {}
loc.x = lastPoint.x
loc.y = 0
loc.z = lastPoint.y
local bestAirbase = dcsCommon.getClosestAirbaseTo(loc)
lastPoint.airdromeId = bestAirbase:getID()
trigger.action.outText("last: adjusted to " .. lastPoint.airdromeId, 30)
end
end
end
end
end
function cloneZones.uniqueNameGroupData(theData)
theData.name = dcsCommon.uuid(theData.name)
local units = theData.units
for idx, aUnit in pairs(units) do
aUnit.name = dcsCommon.uuid(aUnit.name)
end
end
function cloneZones.resolveOwnership(spawnZone, ctry)
if not spawnZone.masterOwner then return ctry end
local masterZone = cfxZones.getZoneByName(spawnZone.masterOwner)
if not masterZone then
trigger.action.outText("+++clnZ: cloner " .. spawnZone.name .. " could not fine master owner <" .. spawnZone.masterOwner .. ">", 30)
return ctry
end
if not masterZone.owner then
return ctry
end
ctry = dcsCommon.getACountryForCoalition(masterZone.owner)
return ctry
end
function cloneZones.spawnWithTemplateForZone(theZone, spawnZone)
--trigger.action.outText("ENTER: Spawn with template " .. theZone.name .. " for spawnZone " .. spawnZone.name, 30)
-- theZone is the zoner with the template
-- spawnZone is the spawner with settings
--if not spawnZone then spawnZone = theZone end
local newCenter = cfxZones.getPoint(spawnZone)
-- calculate zoneDelta, is added to all vectors
local zoneDelta = dcsCommon.vSub(newCenter, theZone.origin)
local spawnedGroups = {}
for idx, aGroupName in pairs(theZone.cloneNames) do
local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(aGroupName)
if rawData.name == aGroupName then
else
trigger.action.outText("Clone: FAILED name check", 30)
end
-- now use raw data to spawn and see if it works outabox
local theCat = cfxMX.catText2ID(cat)
--TODO: if theCat == -1 the group is static, may need to code for that
-- update their position if not spawning to exact same location
cloneZones.updateLocationsInGroupData(rawData, zoneDelta, spawnZone.moveRoute)
-- apply turning
dcsCommon.rotateGroupData(rawData, spawnZone.turn, newCenter.x, newCenter.z)
-- make sure all names (group and units) are unique
cloneZones.uniqueNameGroupData(rawData)
-- see waht country we spawn for
ctry = cloneZones.resolveOwnership(spawnZone, ctry)
local theGroup = coalition.addGroup(ctry, theCat, rawData)
table.insert(spawnedGroups, theGroup)
end
return spawnedGroups
end
function cloneZones.spawnWithCloner(theZone)
if not theZone then
trigger.action.outText("+++clnZ: nil zone on spawnWithCloner", 30)
return
end
if not theZone.cloner then
trigger.action.outText("+++clnZ: spawnWithCloner invoked with non-cloner <" .. theZone.name .. ">", 30)
return
end
-- force spawn with this spawner
local templateZone = theZone
if theZone.source then
-- we use a different zone for templates
-- souce can be a comma separated list
local templateName = theZone.source
if dcsCommon.containsString(templateName, ",") then
local allNames = templateName
local templates = dcsCommon.splitString(templateName, ",")
templateName = dcsCommon.pickRandom(templates)
templateName = dcsCommon.trim(templateName)
if cloneZones.verbose then
trigger.action.outText("+++clnZ: picked random template <" .. templateName .."> for from <" .. allNames .. "> for cloner " .. theZone.name, 30)
end
end
local newTemplate = cloneZones.getCloneZoneByName(templateName)
if not newTemplate then
if cloneZones.verbose then
trigger.action.outText("+++clnZ: no clone source with name <" .. templateName .."> for cloner " .. theZone.name, 30)
end
return
end
templateZone = newTemplate
end
-- make sure our template is filled
if not templateZone.cloneNames then
if cloneZones.verbose then
trigger.action.outText("+++clnZ: clone source template <".. templateZone.name .. "> for clone zone <" .. theZone.name .."> is empty", 30)
end
return
end
-- local myLoc = cfxZones.getPoint(theZone)
local theClones = cloneZones.spawnWithTemplateForZone(templateZone, theZone)
-- reset hasClones so we know our spawns are full and we can
-- detect complete destruction
if theClones and #theClones > 0 then
theZone.hasClones = true
theZone.mySpawns = theClones
end
end
function cloneZones.countLiveUnits(theZone)
if not theZone then return 0 end
if not theZone.mySpawns then return 0 end
local count = 0
for idx, aGroup in pairs(theZone.mySpawns) do
if aGroup:isExist() then
local allUnits = aGroup:getUnits()
for idy, aUnit in pairs(allUnits) do
if aUnit:isExist() and aUnit:getLife() >= 1 then
count = count + 1
end
end
end
end
return count
end
function cloneZones.hasLiveUnits(theZone)
if not theZone then return 0 end
if not theZone.mySpawns then return 0 end
for idx, aGroup in pairs(theZone.mySpawns) do
if aGroup:isExist() then
local allUnits = aGroup:getUnits()
for idy, aUnit in pairs(allUnits) do
if aUnit:isExist() and aUnit:getLife() >= 1 then
return true
end
end
end
end
return false
end
function cloneZones.pollFlag(flagNum, method)
-- we currently ignore method
local num = trigger.misc.getUserFlag(flagNum)
trigger.action.setUserFlag(flagNum, num+1)
end
--
-- UPDATE
--
function cloneZones.update()
timer.scheduleFunction(cloneZones.update, {}, timer.getTime() + 1)
for idx, aZone in pairs(cloneZones.cloners) do
-- see if pulse is running
-- see if we got spawn? command
if aZone.spawnFlag then
local currTriggerVal = trigger.misc.getUserFlag(aZone.spawnFlag)
if currTriggerVal ~= aZone.lastSpawnValue
then
if cloneZones.verbose then
trigger.action.outText("+++clnZ: spawn triggered for <" .. aZone.name .. ">", 30)
end
cloneZones.spawnWithCloner(aZone)
aZone.lastSpawnValue = currTriggerVal
end
end
-- see if we are empty and should signal
if aZone.emptyFlag and aZone.hasClones then
if cloneZones.countLiveUnits(aZone) < 1 then
-- we are depleted. poll flag once, then remember we have
-- polled
cloneZones.pollFlag(aZone.emptyFlag)
aZone.hasClones = false
end
end
end
end
function cloneZones.onStart()
--trigger.action.outText("+++clnZ: Enter atStart", 30)
for idx, theZone in pairs(cloneZones.cloners) do
if theZone.onStart then
if cloneZones.verbose then
trigger.action.outText("+++clnZ: atStart will spawn for <"..theZone.name .. ">", 30)
end
cloneZones.spawnWithCloner(theZone)
end
end
end
--
-- START
--
function cloneZones.readConfigZone()
local theZone = cfxZones.getZoneByName("cloneZonesConfig")
if not theZone then
if cloneZones.verbose then
trigger.action.outText("+++clnZ: NO config zone!", 30)
end
return
end
cloneZones.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
if cloneZones.verbose then
trigger.action.outText("+++clnZ: read config", 30)
end
end
function cloneZones.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx Clone Zones requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx Clone Zones",
cloneZones.requiredLibs) then
return false
end
-- read config
cloneZones.readConfigZone()
-- process cloner Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("cloner")
-- now create an rnd gen for each one and add them
-- to our watchlist
for k, aZone in pairs(attrZones) do
cloneZones.createClonerWithZone(aZone) -- process attribute and add to zone
cloneZones.addCloneZone(aZone) -- remember it so we can smoke it
end
-- run through onStart
cloneZones.onStart()
-- start update
cloneZones.update()
trigger.action.outText("cfx Clone Zones v" .. cloneZones.version .. " started.", 30)
return true
end
-- let's go!
if not cloneZones.start() then
trigger.action.outText("cf/x Clone Zones aborted: missing libraries", 30)
cloneZones = nil
end

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "2.5.2" dcsCommon.version = "2.5.3"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB 2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB - clockPositionOfARelativeToB
@ -58,6 +58,7 @@ dcsCommon.version = "2.5.2"
2.5.1 - added SA-18 Igla manpad to unitIsInfantry() 2.5.1 - added SA-18 Igla manpad to unitIsInfantry()
2.5.2 - added copyArray method 2.5.2 - added copyArray method
- corrected heading in createStaticObjectData - corrected heading in createStaticObjectData
2.5.3 - corrected rotateGroupData bug for cz
--]]-- --]]--
-- dcsCommon is a library of common lua functions -- dcsCommon is a library of common lua functions
@ -1444,7 +1445,10 @@ dcsCommon.version = "2.5.2"
function dcsCommon.rotateGroupData(theGroup, degrees, cx, cz) function dcsCommon.rotateGroupData(theGroup, degrees, cx, cz)
if not cx then cx = 0 end if not cx then cx = 0 end
if not cy then cy = 0 end if not cz then cz = 0 end
local cy = cz
--trigger.action.outText("+++dcsC:rotGrp cy,cy = "..cx .. "," .. cy, 30)
local rads = degrees * 3.14152 / 180 local rads = degrees * 3.14152 / 180
-- turns all units in group around the group's center by degrees. -- turns all units in group around the group's center by degrees.
-- may also need to turn individual units by same amount -- may also need to turn individual units by same amount

263
modules/delayFlags.lua Normal file
View File

@ -0,0 +1,263 @@
delayFlag = {}
delayFlag.version = "1.0.0"
delayFlag.verbose = false
delayFlag.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
delayFlag.flags = {}
--[[--
delay flags - simple flag switch & delay, allows for randomize
and dead man switching
Copyright (c) 2022 by Christian Franz and cf/x AG
Version History
1.0.0 - Initial Version
--]]--
function delayFlag.addDelayZone(theZone)
table.insert(delayFlag.flags, theZone)
end
function delayFlag.getDelayZoneByName(aName)
for idx, aZone in pairs(delayFlag.flags) do
if aName == aZone.name then return aZone end
end
if delayFlag.verbose then
trigger.action.outText("+++dlyF: no delay flag with name <" .. aName ..">", 30)
end
return nil
end
--
-- read attributes
--
--
-- create rnd gen from zone
--
function delayFlag.createTimerWithZone(theZone)
-- delay
theZone.delayMin, theZone.delayMax = cfxZones.getPositiveRangeFromZoneProperty(theZone, "timeDelay", 1) -- same as zone signature
if delayFlag.verbose then
trigger.action.outText("+++dlyF: time delay is <" .. theZone.delayMin .. ", " .. theZone.delayMax .. "> seconds", 30)
end
-- trigger flag
if cfxZones.hasProperty(theZone, "f?") then
theZone.triggerFlag = cfxZones.getStringFromZoneProperty(theZone, "f?", "none")
end
if cfxZones.hasProperty(theZone, "in?") then
theZone.triggerFlag = cfxZones.getStringFromZoneProperty(theZone, "in?", "none")
end
if theZone.triggerFlag then
theZone.lastTriggerValue = trigger.misc.getUserFlag(theZone.triggerFlag) -- save last value
end
theZone.method = cfxZones.getStringFromZoneProperty(theZone, "method", "flip")
-- out flag
if cfxZones.hasProperty(theZone, "out!") then
theZone.outFlag = cfxZones.getNumberFromZoneProperty(theZone, "out!", -1)
end
-- on start
if cfxZones.hasProperty(theZone, "onStart") then
theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false)
end
-- message
if cfxZones.hasProperty(theZone, "message") then
theZone.myMessage = cfxZones.getBoolStringZoneProperty(theZone, "message", "<none>")
end
-- init
theZone.running = false
theZone.timeLimit = -1
end
--
-- do the pulling
--
function delayFlag.pollFlag(theFlag, method)
if delayFlag.verbose then
trigger.action.outText("+++dlyF: polling flag " .. theFlag .. " with " .. method, 30)
end
method = method:lower()
local currVal = trigger.misc.getUserFlag(theFlag)
if method == "inc" or method == "f+1" then
trigger.action.setUserFlag(theFlag, currVal + 1)
elseif method == "dec" or method == "f-1" then
trigger.action.setUserFlag(theFlag, currVal - 1)
elseif method == "off" or method == "f=0" then
trigger.action.setUserFlag(theFlag, 0)
elseif method == "flip" or method == "xor" then
if currVal ~= 0 then
trigger.action.setUserFlag(theFlag, 0)
else
trigger.action.setUserFlag(theFlag, 1)
end
else
if method ~= "on" and method ~= "f=1" then
trigger.action.outText("+++RND: unknown method <" .. method .. "> - using 'on'", 30)
end
-- default: on.
trigger.action.setUserFlag(theFlag, 1)
end
local newVal = trigger.misc.getUserFlag(theFlag)
if delayFlag.verbose then
trigger.action.outText("+++dlyF flag <" .. theFlag .. "> changed from " .. currVal .. " to " .. newVal, 30)
end
end
--
-- update
--
function delayFlag.startDelay(theZone)
-- refresh timer
theZone.running = true
-- set new expiry date
local delayMax = theZone.delayMax
local delayMin = theZone.delayMin
local delay = delayMax
if delayMin ~= delayMax then
-- pick random in range , say 3-7 --> 5 s!
local delayDiff = (delayMax - delayMin) + 1 -- 7-3 + 1
delay = dcsCommon.smallRandom(delayDiff) - 1 --> 0-4
delay = delay + delayMin
if delay > theZone.delayMax then delay = theZone.delayMax end
if delay < 1 then delay = 1 end
if delayFlag.verbose then
trigger.action.outText("+++dlyF: delay " .. theZone.name .. " range " .. delayMin .. "-" .. delayMax .. ": selected " .. delay, 30)
end
end
theZone.timeLimit = timer.getTime() + delay
end
function delayFlag.update()
-- call me in a second to poll triggers
timer.scheduleFunction(delayFlag.update, {}, timer.getTime() + 1)
local now = timer.getTime()
for idx, aZone in pairs(delayFlag.flags) do
-- make sure to re-start before reading time limit
if aZone.triggerFlag then
local currTriggerVal = trigger.misc.getUserFlag(aZone.triggerFlag)
if currTriggerVal ~= aZone.lastTriggerValue
then
if delayFlag.verbose then
if aZone.running then
trigger.action.outText("+++dlyF: re-starting timer " .. aZone.name, 30)
else
trigger.action.outText("+++dlyF: init timer for " .. aZone.name, 30)
end
end
delayFlag.startDelay(aZone) -- we restart even if running
aZone.lastTriggerValue = currTriggerVal
end
end
if aZone.running then
-- check expiry
if now > aZone.timeLimit then
-- end timer
aZone.running = false
-- poll flag
delayFlag.pollFlag(aZone.outFlag, aZone.method)
-- say message
if aZone.myMessage then
trigger.action.outText(aZone.myMessage, 30)
end
end
end
end
end
--
-- START
--
function delayFlag.readConfigZone()
local theZone = cfxZones.getZoneByName("cloneZonesConfig")
if not theZone then
if delayFlag.verbose then
trigger.action.outText("+++dlyF: NO config zone!", 30)
end
return
end
delayFlag.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
if delayFlag.verbose then
trigger.action.outText("+++dlyF: read config", 30)
end
end
function delayFlag.onStart()
for idx, theZone in pairs(delayFlag.flags) do
if theZone.onStart then
if delayFlag.verbose then
trigger.action.outText("+++dlyF: onStart for <"..theZone.name .. ">", 30)
end
delayFlag.startDelay(theZone)
end
end
end
function delayFlag.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("cfx Delay Flags requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx Delay Flags",
delayFlag.requiredLibs) then
return false
end
-- read config
delayFlag.readConfigZone()
-- process cloner Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("timeDelay")
for k, aZone in pairs(attrZones) do
delayFlag.createTimerWithZone(aZone) -- process attributes
delayFlag.addDelayZone(aZone) -- add to list
end
-- kick onStart
delayFlag.onStart()
-- start update
delayFlag.update()
trigger.action.outText("cfx Delay Flag v" .. delayFlag.version .. " started.", 30)
return true
end
-- let's go!
if not delayFlag.start() then
trigger.action.outText("cfx Delay Flag aborted: missing libraries", 30)
delayFlag = nil
end