diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 9aeda28..9257ff6 100644 Binary files a/Doc/DML Documentation.pdf and b/Doc/DML Documentation.pdf differ diff --git a/modules/RNDFlags.lua b/modules/RNDFlags.lua index 475381e..268696c 100644 --- a/modules/RNDFlags.lua +++ b/modules/RNDFlags.lua @@ -278,7 +278,9 @@ end function rndFlags.startCycle() for idx, theZone in pairs(rndFlags.rndGen) do 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) end end diff --git a/modules/cfxMX.lua b/modules/cfxMX.lua new file mode 100644 index 0000000..4caebf4 --- /dev/null +++ b/modules/cfxMX.lua @@ -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 + diff --git a/modules/cfxOwnedZones.lua b/modules/cfxOwnedZones.lua index 5a42228..a6244f8 100644 --- a/modules/cfxOwnedZones.lua +++ b/modules/cfxOwnedZones.lua @@ -1,5 +1,5 @@ cfxOwnedZones = {} -cfxOwnedZones.version = "1.1.0" +cfxOwnedZones.version = "1.1.1" cfxOwnedZones.verbose = false cfxOwnedZones.announcer = true --[[-- VERSION HISTORY @@ -39,6 +39,7 @@ cfxOwnedZones.announcer = true - remove exiting defenders from zone after cap to avoid shocked state - announcer +1.1.1 - conq+1 flag --]]-- cfxOwnedZones.requiredLibs = { @@ -201,6 +202,10 @@ function cfxOwnedZones.addOwnedZone(aZone) local paused = cfxZones.getBoolFromZoneProperty(aZone, "paused", false) 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.untargetable = cfxZones.getBoolFromZoneProperty(aZone, "untargetable", 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") 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 cfxOwnedZones.invokeConqueredCallbacks(aZone, theSide, formerOwner) diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index 291b966..5d638a2 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -6,7 +6,7 @@ -- cfxZones = {} -cfxZones.version = "2.5.2" +cfxZones.version = "2.5.3" --[[-- VERSION HISTORY - 2.2.4 - getCoalitionFromZoneProperty - getStringFromZoneProperty @@ -45,6 +45,9 @@ cfxZones.version = "2.5.2" - 2.5.1 - markZoneWithSmoke supports alt attribute - 2.5.2 - getPoint also writes through to zone itself for optimization - 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 @@ -543,19 +546,28 @@ end -- -- 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 local groupsInZone = {} local allGroups = coalition.getGroups(coal, categ) for key, group in pairs(allGroups) do -- iterate all groups if group:isExist() then - - if cfxZones.isGroupPartiallyInZone(group, theZone) then - - table.insert(groupsInZone, group) - else - - + table.insert(groupsInZone, group) end end end @@ -565,21 +577,14 @@ end function cfxZones.isGroupPartiallyInZone(aGroup, aZone) if not aGroup then return false end if not aZone then return false end - - - -- needs to be implemented + if not aGroup:isExist() then return false end local allUnits = aGroup:getUnits() 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() --- p.y = 0 -- zones have no altitude - -- modification of isPointInsideZone now takes care of this if cfxZones.isPointInsideZone(p, aZone) then return true - else - end end end @@ -589,7 +594,6 @@ end function cfxZones.isEntireGroupInZone(aGroup, aZone) if not aGroup then return false end if not aZone then return false end - -- needs to be implemented if not aGroup:isExist() then return false end local allUnits = aGroup:getUnits() for uk, aUnit in pairs (allUnits) do @@ -1047,6 +1051,8 @@ function cfxZones.getAllZoneProperties(theZone, caseInsensitive) -- return as di end function cfxZones.extractPropertyFromDCS(theKey, theProperties) +-- trim + theKey = dcsCommon.trim(theKey) -- make lower case conversion if not case sensitive if not cfxZones.caseSensitiveProperties then theKey = string.lower(theKey) @@ -1055,7 +1061,8 @@ function cfxZones.extractPropertyFromDCS(theKey, theProperties) -- iterate all keys and compare to what we are looking for for i=1, #theProperties do local theP = theProperties[i] - local existingKey = theP.key + + local existingKey = dcsCommon.trim(theP.key) if not cfxZones.caseSensitiveProperties then existingKey = string.lower(existingKey) end @@ -1073,7 +1080,7 @@ function cfxZones.getZoneProperty(cZone, theKey) end if not theKey then trigger.action.outText("+++zone: no property key in getZoneProperty for zone " .. cZone.name, 30) - breakme.here = 1 +-- breakme.here = 1 return end @@ -1349,6 +1356,8 @@ function cfxZones.init() -- note, all zones with this property are by definition owned zones. -- and hence will be read anyway. this will merely ensure that the -- ownership is established right away + -- unless owned zones module is missing, in which case + -- ownership is still established local pZones = cfxZones.zonesWithProperty("owner") for n, aZone in pairs(pZones) do aZone.owner = cfxZones.getCoalitionFromZoneProperty(aZone, "owner", 0) diff --git a/modules/cloneZone.lua b/modules/cloneZone.lua new file mode 100644 index 0000000..8f43669 --- /dev/null +++ b/modules/cloneZone.lua @@ -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", "") + if theZone.source == "" 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", "") -- note string on number default + end + + if cfxZones.hasProperty(theZone, "masterOwner") then + theZone.masterOwner = cfxZones.getStringFromZoneProperty(theZone, "masterOwner", "") + 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 \ No newline at end of file diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index 75a830b..40a00aa 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.5.2" +dcsCommon.version = "2.5.3" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -58,6 +58,7 @@ dcsCommon.version = "2.5.2" 2.5.1 - added SA-18 Igla manpad to unitIsInfantry() 2.5.2 - added copyArray method - corrected heading in createStaticObjectData + 2.5.3 - corrected rotateGroupData bug for cz --]]-- -- dcsCommon is a library of common lua functions @@ -1444,7 +1445,10 @@ dcsCommon.version = "2.5.2" function dcsCommon.rotateGroupData(theGroup, degrees, cx, cz) 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 -- turns all units in group around the group's center by degrees. -- may also need to turn individual units by same amount diff --git a/modules/delayFlags.lua b/modules/delayFlags.lua new file mode 100644 index 0000000..934c2d5 --- /dev/null +++ b/modules/delayFlags.lua @@ -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", "") + 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 \ No newline at end of file diff --git a/tutorial & demo missions/demo - ADF and NDB fun.miz b/tutorial & demo missions/demo - ADF and NDB fun.miz index f3104c1..3ef08e0 100644 Binary files a/tutorial & demo missions/demo - ADF and NDB fun.miz and b/tutorial & demo missions/demo - ADF and NDB fun.miz differ diff --git a/tutorial & demo missions/demo - Attack of the CloneZ.miz b/tutorial & demo missions/demo - Attack of the CloneZ.miz new file mode 100644 index 0000000..ab01c7c Binary files /dev/null and b/tutorial & demo missions/demo - Attack of the CloneZ.miz differ