diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index aa56b2c..6874369 100644 Binary files a/Doc/DML Documentation.pdf and b/Doc/DML Documentation.pdf differ diff --git a/Doc/DML Quick Reference.pdf b/Doc/DML Quick Reference.pdf new file mode 100644 index 0000000..55e4bc9 Binary files /dev/null and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/FARPZones.lua b/modules/FARPZones.lua index 42880b9..faa56e4 100644 --- a/modules/FARPZones.lua +++ b/modules/FARPZones.lua @@ -178,13 +178,8 @@ function FARPZones.createFARPFromZone(aZone) return theFarp end ---[[-- -function FARPZones.drawFarp(theFarp) - local theZone = theFarp.zone - local theOwner = theFarp.owner - FARPZones.drawZoneInMap(theZone, theOwner) -end ---]]-- + + function FARPZones.drawFARPCircleInMap(theFarp) if not theFarp then return end @@ -430,6 +425,7 @@ function FARPZones.readConfig() FARPZones.spinUpDelay = cfxZones.getNumberFromZoneProperty(theZone, "spinUpDelay", 30) + if FARPZones.verbose then trigger.action.outText("***frpZ: read config", 30) end diff --git a/modules/cfxArtilleryZones.lua b/modules/cfxArtilleryZones.lua index a1b6284..334872d 100644 --- a/modules/cfxArtilleryZones.lua +++ b/modules/cfxArtilleryZones.lua @@ -1,5 +1,5 @@ cfxArtilleryZones = {} -cfxArtilleryZones.version = "2.0.1" +cfxArtilleryZones.version = "2.0.2" cfxArtilleryZones.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course @@ -24,7 +24,8 @@ cfxArtilleryZones.verbose = false - att transition time to zone info mark - made compatible with linked zones - added silent attribute - - added transition time to arty command chatter + - added transition time to arty command chatter + 2.0.2 - boom?, arty? synonyms Artillery Target Zones *** EXTENDS ZONES *** Target Zones for artillery. Can determine which zones are in range and visible and then handle artillery barrage to this zone @@ -121,13 +122,18 @@ function cfxArtilleryZones.processArtilleryZone(aZone) aZone.addMark = cfxZones.getBoolFromZoneProperty(aZone, "addMark", true) -- note: defaults to true aZone.shellVariance = cfxZones.getNumberFromZoneProperty(aZone, "shellVariance", 0.2) -- strength of explosion can vary by +/- this amount if cfxZones.hasProperty(aZone, "f?") then - aZone.triggerFlag = cfxZones.getStringFromZoneProperty(aZone, "f?", "none") + aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "f?", "none") end + --[[-- if cfxZones.hasProperty(aZone, "triggerFlag") then - aZone.triggerFlag = cfxZones.getStringFromZoneProperty(aZone, "triggerFlag", "none") + aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "triggerFlag", "none") end - if aZone.triggerFlag then - aZone.lastTriggerValue = trigger.misc.getUserFlag(aZone.triggerFlag) -- save last value + --]]-- + if cfxZones.hasProperty(aZone, "artillery?") then + aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "artillery?", "none") + end + if aZone.artyTriggerFlag then + aZone.lastTriggerValue = trigger.misc.getUserFlag(aZone.artyTriggerFlag) -- save last value end aZone.cooldown =cfxZones.getNumberFromZoneProperty(aZone, "cooldown", 120) -- seconds aZone.baseAccuracy = cfxZones.getNumberFromZoneProperty(aZone, "baseAccuracy", aZone.radius) -- meters from center radius shell impact @@ -380,8 +386,8 @@ function cfxArtilleryZones.update() -- iterate all zones to see if a trigger has changed for idx, aZone in pairs(cfxArtilleryZones.artilleryZones) do - if aZone.triggerFlag then - local currTriggerVal = trigger.misc.getUserFlag(aZone.triggerFlag) + if aZone.artyTriggerFlag then + local currTriggerVal = trigger.misc.getUserFlag(aZone.artyTriggerFlag) if currTriggerVal ~= aZone.lastTriggerValue then -- a triggered release! diff --git a/modules/cfxCargoReceiver.lua b/modules/cfxCargoReceiver.lua index 9936ae3..12a9028 100644 --- a/modules/cfxCargoReceiver.lua +++ b/modules/cfxCargoReceiver.lua @@ -1,5 +1,5 @@ cfxCargoReceiver = {} -cfxCargoReceiver.version = "1.1.0" +cfxCargoReceiver.version = "1.2.0" cfxCargoReceiver.ups = 1 -- once a second cfxCargoReceiver.maxDirectionRange = 500 -- in m. distance when cargo manager starts talking to pilots who are carrying that cargo cfxCargoReceiver.requiredLibs = { @@ -13,7 +13,9 @@ cfxCargoReceiver.requiredLibs = { - 1.0.0 initial vbersion - 1.1.0 added flag manipulation options no negative agl on announcement - silent attribute + silent attribute + - 1.2.0 method + f!, cargoReceived! CargoReceiver is a zone enhancement you use to be automatically @@ -67,6 +69,17 @@ function cfxCargoReceiver.processReceiverZone(aZone) -- process attribute and ad if cfxZones.hasProperty(aZone, "f-1") then aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999") end + + -- new method support + aZone.method = cfxZones.getStringFromZoneProperty(aZone, "method", "inc") + + if cfxZones.hasProperty(aZone, "f!") then + aZone.outReceiveFlag = cfxZones.getNumberFromZoneProperty(aZone, "f!", -1) + end + if cfxZones.hasProperty(aZone, "cargoReceived!") then + aZone.outReceiveFlag = cfxZones.getNumberFromZoneProperty(aZone, "cargoReceived!", -1) + end + end function cfxCargoReceiver.addReceiverZone(aZone) @@ -132,6 +145,9 @@ function cfxCargoReceiver.cargoEvent(event, object, name) trigger.action.setUserFlag(aZone.decreaseFlag, val) end + if aZone.outReceiveFlag then + cfxZones.pollFlag(aZone.outReceiveFlag, aZone.method) + end --trigger.action.outText("+++rcv: " .. name .. " delivered in zone " .. aZone.name, 30) --trigger.action.outSound("Quest Snare 3.wav") diff --git a/modules/cfxMX.lua b/modules/cfxMX.lua index 8db19f5..7a03d64 100644 --- a/modules/cfxMX.lua +++ b/modules/cfxMX.lua @@ -1,5 +1,5 @@ cfxMX = {} -cfxMX.version = "1.0.1" +cfxMX.version = "1.1.0" --[[-- Mission data decoder. Access to ME-built mission structures @@ -8,9 +8,17 @@ cfxMX.version = "1.0.1" Version History 1.0.0 - initial version 1.0.1 - getStaticFromDCSbyName() + 1.1.0 - getStaticFromDCSbyName also copies groupID when not fetching orig + - on start up collects a cross reference table of all + original group id + - add linkUnit for statics --]]-- +cfxMX.groupNamesByID = {} +cfxMX.groupIDbyName = {} +cfxMX.groupDataByName = {} + function cfxMX.getGroupFromDCSbyName(aName, fetchOriginal) if not fetchOriginal then fetchOriginal = false end @@ -105,6 +113,15 @@ function cfxMX.getStaticFromDCSbyName(aName, fetchOriginal) 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 static in group! for group_num, group_data in pairs(obj_type_data.group) do + -- get linkUnit info if it exists + local linkUnit = nil + if group_data and group_data.route and group_data.route and group_data.route.points[1] then + linkUnit = group_data.route.points[1].linkUnit + if linkUnit then + --trigger.action.outText("MX: found missing link to " .. linkUnit .. " in " .. group_data.name, 30) + end + end + if group_data and group_data.units and type(group_data.units) == 'table' then --make sure - again - that this is a valid group for unit_num, unit_data in pairs(group_data.units) do -- iterate units @@ -113,6 +130,11 @@ function cfxMX.getStaticFromDCSbyName(aName, fetchOriginal) local theStatic = unit_data if not fetchOriginal then theStatic = dcsCommon.clone(unit_data) + -- copy group ID from group above + theStatic.groupId = group_data.groupId + -- copy linked unit data + theStatic.linkUnit = linkUnit + end return theStatic, category, countryID, groupName @@ -132,6 +154,51 @@ function cfxMX.getStaticFromDCSbyName(aName, fetchOriginal) return nil, "", "", "" end +function cfxMX.createCrossReference() + 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 + local aName = group_data.name + local aID = group_data.groupId + cfxMX.groupNamesByID[aID] = aName + cfxMX.groupIDbyName[aName] = aID + cfxMX.groupDataByName[aName] = group_data + 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 +end + function cfxMX.catText2ID(inText) local outCat = 0 -- airplane local c = inText:lower() @@ -144,3 +211,11 @@ function cfxMX.catText2ID(inText) return outCat end +function cfxMX.start() + cfxMX.createCrossReference() + trigger.action.outText("cfxMX: "..#cfxMX.groupNamesByID .. " groups processed successfully", 30) +end + +-- start +cfxMX.start() + diff --git a/modules/cfxObjectDestructDetector.lua b/modules/cfxObjectDestructDetector.lua index 3dd101b..7c0ab7e 100644 --- a/modules/cfxObjectDestructDetector.lua +++ b/modules/cfxObjectDestructDetector.lua @@ -1,5 +1,5 @@ cfxObjectDestructDetector = {} -cfxObjectDestructDetector.version = "1.0.1" +cfxObjectDestructDetector.version = "1.1.0" cfxObjectDestructDetector.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course @@ -9,6 +9,8 @@ cfxObjectDestructDetector.verbose = false VERSION HISTORY 1.0.0 initial version, based on parashoo, arty zones 1.0.1 fixed bug: trigger.MISC.getUserFlag() + 1.1.0 added support for method, f! and destroyed! + Detect when an object with OBJECT ID as assigned in ME dies *** EXTENDS ZONES @@ -69,6 +71,16 @@ function cfxObjectDestructDetector.processObjectDestructZone(aZone) if cfxZones.hasProperty(aZone, "f-1") then aZone.decreaseFlag = cfxZones.getStringFromZoneProperty(aZone, "f-1", "999") end + + -- new method support + aZone.method = cfxZones.getStringFromZoneProperty(aZone, "method", "flip") + + if cfxZones.hasProperty(aZone, "f!") then + aZone.outDestroyFlag = cfxZones.getNumberFromZoneProperty(aZone, "f!", -1) + end + if cfxZones.hasProperty(aZone, "destroyed!") then + aZone.outDestroyFlag = cfxZones.getNumberFromZoneProperty(aZone, "destroyed!", -1) + end end -- -- MAIN DETECTOR @@ -98,6 +110,11 @@ function cfxObjectDestructDetector:onEvent(event) trigger.action.setUserFlag(aZone.decreaseFlag, val) end + -- support for banging + if aZone.outDestroyFlag then + cfxZones.pollFlag(aZone.outDestroyFlag, aZone.method) + end + -- invoke callbacks cfxObjectDestructDetector.invokeCallbacksFor(aZone) if cfxObjectDestructDetector.verbose then diff --git a/modules/cfxObjectSpawnZones.lua b/modules/cfxObjectSpawnZones.lua index b24f8ac..d5a38af 100644 --- a/modules/cfxObjectSpawnZones.lua +++ b/modules/cfxObjectSpawnZones.lua @@ -1,5 +1,5 @@ cfxObjectSpawnZones = {} -cfxObjectSpawnZones.version = "1.1.4" +cfxObjectSpawnZones.version = "1.1.5" cfxObjectSpawnZones.requiredLibs = { "dcsCommon", -- common is of course needed for everything -- pretty stupid to check for this since we @@ -23,6 +23,7 @@ cfxObjectSpawnZones.ups = 1 -- - added possibility to autoUnlink -- 1.1.3 - ME-triggered flag via f? and triggerFlag -- 1.1.4 - activate?, pause? attributes +-- 1.1.5 - spawn?, spawnObjects? synonyms -- Object spawn zones have the following major uses: -- - dynamically spawn cargo @@ -85,6 +86,16 @@ function cfxObjectSpawnZones.createSpawner(inZone) theSpawner.lastTriggerValue = trigger.misc.getUserFlag(theSpawner.triggerFlag) end + if cfxZones.hasProperty(inZone, "spawn?") then + theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawn?", "none") + theSpawner.lastTriggerValue = trigger.misc.getUserFlag(theSpawner.triggerFlag) + end + + if cfxZones.hasProperty(inZone, "spawnObjects?") then + theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawnObjects?", "none") + theSpawner.lastTriggerValue = trigger.misc.getUserFlag(theSpawner.triggerFlag) + end + if cfxZones.hasProperty(inZone, "activate?") then theSpawner.activateFlag = cfxZones.getStringFromZoneProperty(inZone, "activate?", "none") theSpawner.lastActivateValue = trigger.misc.getUserFlag(theSpawner.activateFlag) diff --git a/modules/cfxSmokeZones.lua b/modules/cfxSmokeZones.lua index f3800e1..96a306f 100644 --- a/modules/cfxSmokeZones.lua +++ b/modules/cfxSmokeZones.lua @@ -1,5 +1,5 @@ cfxSmokeZone = {} -cfxSmokeZone.version = "1.0.3" +cfxSmokeZone.version = "1.0.4" cfxSmokeZone.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course @@ -12,7 +12,10 @@ cfxSmokeZone.requiredLibs = { 1.0.3 - added paused attribute - added f? attribute --> onFlag - broke out startSmoke - + 1.0.4 - startSmoke? synonym + - alphanum flag upgrade + - random color support + SMOKE ZONES *** EXTENDS ZONES *** keeps 'eternal' smoke up for any zone that has the 'smoke' attribute @@ -34,6 +37,9 @@ function cfxSmokeZone.processSmokeZone(aZone) if rawVal == "white" or rawVal == "2" then theColor = 2 end if rawVal == "orange" or rawVal == "3" then theColor = 3 end if rawVal == "blue" or rawVal == "4" then theColor = 4 end + if rawVal == "?" or rawVal == "random" or rawVal == "rnd" then + theColor = dcsCommon.smallRandom(5) - 1 + end aZone.smokeColor = theColor aZone.smokeAlt = cfxZones.getNumberFromZoneProperty(aZone, "altitude", 1) @@ -46,8 +52,12 @@ function cfxSmokeZone.processSmokeZone(aZone) aZone.onFlag = cfxZones.getStringFromZoneProperty(aZone, "f?", "none") end + if cfxZones.hasProperty(aZone, "startSmoke?") then + aZone.onFlag = cfxZones.getStringFromZoneProperty(aZone, "startSmoke?", "none") + end + if aZone.onFlag then - aZone.onFlagVal = trigger.misc.getUserFlag(aZone.onFlag) -- save last value + aZone.onFlagVal = cfxZones.getFlagValue(aZone.onFlag, aZone) -- save last value end end @@ -67,7 +77,7 @@ function cfxSmokeZone.addSmokeZoneWithColor(aZone, aColor, anAltitude, paused, o if onFlag then aZone.onFlag = onFlag - aZone.onFlagVal = trigger.misc.getUserFlag(onFlag) + aZone.onFlagVal = cfxZones.getFlagValue(aZone.onFlag, aZone) -- trigger.misc.getUserFlag(onFlag) end cfxSmokeZone.addSmokeZone(aZone) -- add to update loop @@ -123,7 +133,7 @@ function cfxSmokeZone.checkFlags() for idx, aZone in pairs(cfxSmokeZone.smokeZones) do if aZone.paused and aZone.onFlagVal then -- see if this changed - local currTriggerVal = trigger.misc.getUserFlag(aZone.onFlag) + local currTriggerVal = cfxZones.getFlagValue(aZone.onFlag, aZone) -- trigger.misc.getUserFlag(aZone.onFlag) if currTriggerVal ~= aZone.onFlagVal then -- yupp, trigger start cfxSmokeZone.startSmoke(aZone) diff --git a/modules/cfxSpawnZones.lua b/modules/cfxSpawnZones.lua index 620a707..e6b935e 100644 --- a/modules/cfxSpawnZones.lua +++ b/modules/cfxSpawnZones.lua @@ -1,5 +1,5 @@ cfxSpawnZones = {} -cfxSpawnZones.version = "1.5.2" +cfxSpawnZones.version = "1.5.3" cfxSpawnZones.requiredLibs = { "dcsCommon", -- common is of course needed for everything -- pretty stupid to check for this since we @@ -52,6 +52,7 @@ cfxSpawnZones.verbose = false -- 1.5.1 - relaxed baseName and default to dcsCommon.uuid() -- - verbose -- 1.5.2 - activate?, pause? flag +-- 1.5.3 - spawn?, spawnUnits? flags -- -- new version requires cfxGroundTroops, where they are -- @@ -140,6 +141,17 @@ function cfxSpawnZones.createSpawner(inZone) theSpawner.lastTriggerValue = trigger.misc.getUserFlag(theSpawner.triggerFlag) end + -- synonyms spawn? and spawnObject? + if cfxZones.hasProperty(inZone, "spawn?") then + theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawn?", "none") + theSpawner.lastTriggerValue = trigger.misc.getUserFlag(theSpawner.triggerFlag) + end + + if cfxZones.hasProperty(inZone, "spawnUnits?") then + theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "spawnObject?", "none") + theSpawner.lastTriggerValue = trigger.misc.getUserFlag(theSpawner.triggerFlag) + end + if cfxZones.hasProperty(inZone, "activate?") then theSpawner.activateFlag = cfxZones.getStringFromZoneProperty(inZone, "activate?", "none") theSpawner.lastActivateValue = trigger.misc.getUserFlag(theSpawner.activateFlag) diff --git a/modules/cfxZones.lua b/modules/cfxZones.lua index 7d8a849..0cfb669 100644 --- a/modules/cfxZones.lua +++ b/modules/cfxZones.lua @@ -6,7 +6,7 @@ -- cfxZones = {} -cfxZones.version = "2.5.5" +cfxZones.version = "2.5.6" --[[-- VERSION HISTORY - 2.2.4 - getCoalitionFromZoneProperty - getStringFromZoneProperty @@ -50,6 +50,10 @@ cfxZones.version = "2.5.5" - extractPropertyFromDCS trims key and property - 2.5.5 - pollFlag() centralized for banging - allStaticsInZone + - 2.5.6 - flag accessor setFlagValue(), getFlagValue + - pollFlag supports theZone as final parameter + - randomDelayFromPositiveRange + - isMEFlag --]]-- cfxZones.verbose = false @@ -197,6 +201,7 @@ function cfxZones.readFromDCS(clearfirst) -- add to my table cfxZones.zones[upperName] = newZone -- WARNING: UPPER ZONE!!! + --trigger.action.outText("znd: procced " .. newZone.name .. " with radius " .. newZone.radius, 30) else if cfxZones.verbose then trigger.action.outText("cf/x zones: malformed zone #" .. i .. " dropped", 10) @@ -467,11 +472,13 @@ end; function cfxZones.isPointInsideZone(thePoint, theZone) local p = {x=thePoint.x, y = 0, z = thePoint.z} -- zones have no altitude if (theZone.isCircle) then + local zp = cfxZones.getPoint(theZone) local d = dcsCommon.dist(p, theZone.point) return d < theZone.radius end if (theZone.isPoly) then + --trigger.action.outText("zne: isPointInside: " .. theZone.name .. " is Polyzone!", 30) return (cfxZones.isPointInsidePoly(p, theZone.poly)) end @@ -550,6 +557,7 @@ end -- function cfxZones.allGroupsInZone(theZone, categ) -- categ is optional, must be code -- warning: does not check for exiting! + --trigger.action.outText("Zone " .. theZone.name .. " radius " .. theZone.radius, 30) local inZones = {} local coals = {0, 1, 2} -- all coalitions for idx, coa in pairs(coals) do @@ -601,9 +609,12 @@ function cfxZones.isGroupPartiallyInZone(aGroup, aZone) for uk, aUnit in pairs (allUnits) do if aUnit:isExist() and aUnit:getLife() > 1 then local p = aUnit:getPoint() - if cfxZones.isPointInsideZone(p, aZone) then + local inzone, percent, dist = cfxZones.pointInZone(p, aZone) + if inzone then -- cfxZones.isPointInsideZone(p, aZone) then + --trigger.action.outText("zne: YAY <" .. aUnit:getName() .. "> IS IN " .. aZone.name, 30) return true end + --trigger.action.outText("zne: <" .. aUnit:getName() .. "> not in " .. aZone.name .. ", dist = " .. dist .. ", rad = ", aZone.radius, 30) end end return false @@ -1048,7 +1059,7 @@ end -- -- Flag Pulling -- -function cfxZones.pollFlag(theFlag, method) +function cfxZones.pollFlag(theFlag, method, theZone) if cfxZones.verbose then trigger.action.outText("+++zones: polling flag " .. theFlag .. " with " .. method, 30) end @@ -1085,6 +1096,68 @@ function cfxZones.pollFlag(theFlag, method) end end +function cfxZones.setFlagValue(theFlag, theValue, theZone) + local zoneName = "" + if not theZone then + trigger.action.outText("+++Zne: no zone on setFlagValue") + else + zoneName = theZone.name -- for flag wildcards + end + + if type(theFlag) == "number" then + -- straight set, ME flag + trigger.action.setUserFlag(theFlag, theValue) + return + end + + -- we assume it's a string now + theFlag = dcsCommon.trim(theFlag) -- clear leading/trailing spaces + local nFlag = tonumber(theFlag) + if nFlag then + trigger.action.setUserFlag(theFlag, theValue) + return + end + + -- now do wildcard processing. we have alphanumeric + if dcsCommon.stringStartsWith(theFlag, "*") then + theFlag = zoneName .. theFlag + end + trigger.action.setUserFlag(theFlag, theValue) +end + +function cfxZones.getFlagValue(theFlag, theZone) + local zoneName = "" + if not theZone then + trigger.action.outText("+++Zne: no zone on getFlagValue") + else + zoneName = theZone.name -- for flag wildcards + end + + if type(theFlag) == "number" then + -- straight get, ME flag + return trigger.misc.getUserFlag(theFlag) + end + + -- we assume it's a string now + theFlag = dcsCommon.trim(theFlag) -- clear leading/trailing spaces + local nFlag = tonumber(theFlag) + if nFlag then + return trigger.misc.getUserFlag(theFlag) + end + + -- now do wildcard processing. we have alphanumeric + if dcsCommon.stringStartsWith(theFlag, "*") then + theFlag = zoneName .. theFlag + end + return trigger.misc.getUserFlag(theFlag) +end + +function cfxZones.isMEFlag(inFlag) + return true + -- returns true if inFlag is a pure positive number +-- inFlag = dcsCommon.trim(inFlag) +-- return dcsCommon.stringIsPositiveNumber(inFlag) +end -- -- PROPERTY PROCESSING @@ -1167,6 +1240,19 @@ function cfxZones.getMinMaxFromZoneProperty(theZone, theProperty) end +function cfxZones.randomDelayFromPositiveRange(minVal, maxVal) + if not maxVal then return minVal end + if not minVal then return maxVal end + local delay = maxVal + if minVal > 0 and minVal < delay then + -- we want a randomized from time from minTime .. delay + local varPart = delay - minVal + 1 + varPart = dcsCommon.smallRandom(varPart) - 1 + delay = minVal + varPart + end + return delay +end + function cfxZones.getPositiveRangeFromZoneProperty(theZone, theProperty, default) -- reads property as string, and interprets as range 'a-b'. -- if not a range but single number, returns both for upper and lower diff --git a/modules/cloneZone.lua b/modules/cloneZone.lua index 23a12de..90873c2 100644 --- a/modules/cloneZone.lua +++ b/modules/cloneZone.lua @@ -1,5 +1,5 @@ cloneZones = {} -cloneZones.version = "1.1.1" +cloneZones.version = "1.2.0" cloneZones.verbose = false cloneZones.requiredLibs = { "dcsCommon", -- always @@ -7,7 +7,10 @@ cloneZones.requiredLibs = { "cfxMX", } cloneZones.cloners = {} - +cloneZones.callbacks = {} +cloneZones.unitXlate = {} +cloneZones.groupXlate = {} -- used to translate original groupID to cloned. only holds last spawned group id +cloneZones.uniqueCounter = 9200000 -- we start group numbering here --[[-- Clones Groups from ME mission data Copyright (c) 2022 by Christian Franz and cf/x AG @@ -18,6 +21,16 @@ cloneZones.cloners = {} 1.1.0 - support for static objects - despawn? attribute 1.1.1 - despawnAll: isExist guard + - map in? to f? + 1.2.0 - Lua API integration: callbacks + - groupXlate struct + - unitXlate struct + - resolveReferences + - getGroupsInZone rewritten for data + - static resolve + - linkUnit resolve + - clone? synonym + - empty! and method attributes --]]-- @@ -38,18 +51,82 @@ function cloneZones.getCloneZoneByName(aName) return nil end + +-- +-- callbacks +-- + +function cloneZones.addCallback(theCallback) + if not theCallback then return end + table.insert(cloneZones.callbacks, theCallback) +end + +-- reasons for callback +-- "will despawn group" - args is the group about to be despawned +-- "did spawn group" -- args is group that was spawned +-- "will despawn static" +-- "did spawn static" +-- "spawned" -- completed spawn cycle. args contains .groups and .statics spawned +-- "empty" -- all spawns have been killed, args is empty +-- "wiped" -- preWipe executed +-- "") -- note string on number default end + if cfxZones.hasProperty(theZone, "empty!") then + theZone.emptyBangFlag = cfxZones.getNumberFromZoneProperty(theZone, "empty!", "") -- note string on number default + end + + theZone.method = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") + 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 + -- we end with clear plate end -- @@ -148,7 +241,10 @@ function cloneZones.despawnAll(theZone) trigger.action.outText("wiping <" .. theZone.name .. ">", 30) end for idx, aGroup in pairs(theZone.mySpawns) do + --trigger.action.outText("++clnZ: despawn all " .. aGroup.name, 30) + if aGroup:isExist() then + cloneZones.invokeCallbacks(theZone, "will despawn group", aGroup) Group.destroy(aGroup) end end @@ -156,7 +252,10 @@ function cloneZones.despawnAll(theZone) -- warning! may be mismatch because we are looking at groups -- not objects. let's see if aStatic:isExist() then - trigger.action.outText("Destroying static <" .. aStatic:getName() .. ">", 30) + if cloneZones.verbose then + trigger.action.outText("Destroying static <" .. aStatic:getName() .. ">", 30) + end + cloneZones.invokeCallbacks(theZone, "will despawn static", aStatic) Object.destroy(aStatic) -- we don't aStatio:destroy() to find out what it is end end @@ -165,7 +264,7 @@ function cloneZones.despawnAll(theZone) 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 -- !!! @@ -178,8 +277,6 @@ function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWayp -- 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 @@ -227,8 +324,13 @@ function cloneZones.updateLocationsInGroupData(theData, zoneDelta, adjustAllWayp end end end +function cloneZones.uniqueID() + local uid = cloneZones.uniqueCounter + cloneZones.uniqueCounter = cloneZones.uniqueCounter + 1 + return uid +end -function cloneZones.uniqueNameGroupData(theData) +function cloneZones.uniqueNameGroupData(theData) theData.name = dcsCommon.uuid(theData.name) local units = theData.units for idx, aUnit in pairs(units) do @@ -236,6 +338,21 @@ function cloneZones.uniqueNameGroupData(theData) end end +function cloneZones.uniqueIDGroupData(theData) + theData.groupId = cloneZones.uniqueID() +end + +function cloneZones.uniqueIDUnitData(theData) + if not theData then return end + if not theData.units then return end + local units = theData.units + for idx, aUnit in pairs(units) do + aUnit.CZorigID = aUnit.unitId + aUnit.unitId = cloneZones.uniqueID() + aUnit.CZTargetID = aUnit.unitId + end + +end function cloneZones.resolveOwnership(spawnZone, ctry) if not spawnZone.masterOwner then return ctry end @@ -254,9 +371,151 @@ function cloneZones.resolveOwnership(spawnZone, ctry) return ctry end +-- +-- resolve external group references +-- + +function cloneZones.resolveGroupID(gID, rawData, dataTable, reason) + local resolvedID = gID + local myOName = rawData.CZorigName + local groupName = cfxMX.groupNamesByID[gID] + --trigger.action.outText("Resolve for <" .. myOName .. "> the external ID: " .. gID .. " --> " .. groupName .. " for <" .. reason.. "> task", 30) + + -- first, check if this an internal reference, i.e. inside the same + -- zone template + for idx, otherData in pairs(dataTable) do + -- look in own data table + if otherData.CZorigName == groupName then + -- using cfxMX for clarity only (name access) + resolvedID = otherData.CZTargetID + --trigger.action.outText("resolved (internally) " .. gID .. " to " .. resolvedID, 30) + return resolvedID + end + end + + -- now check if we have spawned this before + local lastClone = cloneZones.groupXlate[gID] + if lastClone then + resolvedID = lastClone + --trigger.action.outText("resolved (EXT) " .. gID .. " to " .. resolvedID, 30) + return resolvedID + end + + -- if we get here, reference is not to a cloned item + --trigger.action.outText("resolved " .. gID .. " to " .. resolvedID, 30) + return resolvedID +end + +function cloneZones.resolveUnitID(uID, rawData, dataTable, reason) +-- also resolves statics as they share ID with units + local resolvedID = uID + --trigger.action.outText("Resolve reference to unitId <" .. uID .. "> for <" .. reason.. "> task", 30) + + -- first, check if this an internal reference, i.e. inside the same + -- zone template + for idx, otherData in pairs(dataTable) do + -- iterate all units + for idy, aUnit in pairs(otherData.units) do + if aUnit.CZorigID == uID then + resolvedID = aUnit.CZTargetID + --trigger.action.outText("resolved (internally) " .. uID .. " to " .. resolvedID, 30) + return resolvedID + end + end + + end + + -- now check if we have spawned this before + local lastClone = cloneZones.unitXlate[uID] + if lastClone then + resolvedID = lastClone + --trigger.action.outText("resolved (U-EXT) " .. uID .. " to " .. resolvedID, 30) + return resolvedID + end + + -- if we get here, reference is not to a cloned item + --trigger.action.outText("resolved G-" .. uID .. " to " .. resolvedID, 30) + return resolvedID +end + +function cloneZones.resolveStaticLinkUnit(uID) + local resolvedID = uID + local lastClone = cloneZones.unitXlate[uID] + if lastClone then + resolvedID = lastClone + --trigger.action.outText("resolved (U-EXT) " .. uID .. " to " .. resolvedID, 30) + return resolvedID + end + return resolvedID +end + +function cloneZones.resolveWPReferences(rawData, theZone, dataTable) +-- check to see if we really need data table, as we have theZone +-- perform a check of route for group or unit references + if not rawData then return end + local myOName = rawData.CZorigName + + if rawData.route and rawData.route.points then + local points = rawData.route.points + for idx, aPoint in pairs(points) do + -- check if there is a link unit here and resolve + if aPoint.linkUnit then + local gID = aPoint.linkUnit + local resolvedID = cloneZones.resolveUnitID(gID, rawData, dataTable, "linkUnit") + aPoint.linkUnit = resolvedID + --trigger.action.outText("resolved link unit to "..resolvedID .. " for " .. rawData.name, 30) + end + + -- iterate all tasks assigned to point + local task = aPoint.task + if task and task.params and task.params.tasks then + local tasks = task.params.tasks + for idy, taskData in pairs(tasks) do + -- resolve group references in TASKS + if taskData.id and taskData.params and taskData.params.groupId + then + -- we resolve group reference + local gID = taskData.params.groupId + local resolvedID = cloneZones.resolveGroupID(gID, rawData, dataTable, taskData.id) + taskData.params.groupId = resolvedID + + end + + -- resolve unit references in TASKS + if taskData.id and taskData.params and taskData.params.unitId + then + -- we don't look for keywords, we simply resolve + local uID = taskData.params.unitId + local resolvedID = cloneZones.resolveUnitID(uID, rawData, dataTable, taskData.id) + taskData.params.unitId = resolvedID + end + + -- resolve unit references in ACTIONS + if taskData.params and taskData.params.action and + taskData.params.action.params and taskData.params.action.params.unitId then + local uID = taskData.params.action.params.unitId + local resolvedID = cloneZones.resolveUnitID(uID, rawData, dataTable, "Action") + taskData.params.action.params.unitId = resolvedID + end + end + end + end + end +end + +function cloneZones.resolveReferences(theZone, dataTable) + -- when an action refers to another group, we check if + -- the group referred to is also a clone, and update + -- the reference to the newest incardnation + + for idx, rawData in pairs(dataTable) do + -- resolve references in waypoints + cloneZones.resolveWPReferences(rawData, theZone, dataTable) + end +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 + -- theZone is the cloner with the template -- spawnZone is the spawner with settings --if not spawnZone then spawnZone = theZone end local newCenter = cfxZones.getPoint(spawnZone) @@ -265,17 +524,23 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) local spawnedGroups = {} local spawnedStatics = {} + local dataToSpawn = {} for idx, aGroupName in pairs(theZone.cloneNames) do local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(aGroupName) - - if rawData.name == aGroupName then - else + rawData.CZorigName = rawData.name -- save original group name + local origID = rawData.groupId -- save original group ID + rawData.CZorigID = origID + cloneZones.uniqueIDGroupData(rawData) -- assign unique ID we know + cloneZones.uniqueIDUnitData(rawData) -- assign unique ID for units -- saves old unitId as CZorigID + rawData.CZTargetID = rawData.groupId -- save + if rawData.name ~= aGroupName then 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) + rawData.CZtheCat = theCat -- save cat -- update their position if not spawning to exact same location cloneZones.updateLocationsInGroupData(rawData, zoneDelta, spawnZone.moveRoute) @@ -286,29 +551,76 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) -- make sure unit and group names are unique cloneZones.uniqueNameGroupData(rawData) - -- see waht country we spawn for + -- see what country we spawn for ctry = cloneZones.resolveOwnership(spawnZone, ctry) - - local theGroup = coalition.addGroup(ctry, theCat, rawData) + rawData.CZctry = ctry -- save ctry + table.insert(dataToSpawn, rawData) + end + + -- now resolve references to other cloned units for all raw data + -- we must do this BEFORE we spawn + cloneZones.resolveReferences(theZone, dataToSpawn) + + -- now spawn all raw data + for idx, rawData in pairs (dataToSpawn) do + -- now spawn and save to clones + local theGroup = coalition.addGroup(rawData.CZctry, rawData.CZtheCat, rawData) table.insert(spawnedGroups, theGroup) + + --trigger.action.outText("spawned group " .. rawData.name .. "consisting of", 30) + + -- update groupXlate table + local newGroupID = theGroup:getID() -- new ID assigned by DCS + local origID = rawData.CZorigID -- before we materialized + cloneZones.groupXlate[origID] = newGroupID + -- now also save all units for references + -- and verify assigned vs target ID + for idx, aUnit in pairs(rawData.units) do + -- access the proposed name + local uName = aUnit.name + local gUnit = Unit.getByName(uName) + if gUnit then + -- unit exists. compare planned and assigned ID + local uID = tonumber(gUnit:getID()) + if uID == aUnit.CZTargetID then + --trigger.action.outText("unit " .. uName .. "#"..uID, 30) + -- all good + else + trigger.action.outText("clnZ: post-clone verification failed for unit <" .. uName .. ">: ÎD mismatch: " .. uID .. " -- " .. aUnit.CZTargetID, 30) + end + cloneZones.unitXlate[aUnit.CZorigID] = uID + else + trigger.action.outText("clnZ: post-clone verifiaction failed for unit <" .. uName .. ">: not found", 30) + end + end + + -- check if our assigned ID matches the handed out by + -- DCS + if newGroupID == rawData.CZTargetID then + -- we are good + else + trigger.action.outText("clnZ: MISMATCH " .. rawData.name .. " target ID " .. rawData.CZTargetID .. " does not match " .. newGroupID, 30) + end + + cloneZones.invokeCallbacks(theZone, "did spawn group", theGroup) end -- static spawns for idx, aStaticName in pairs(theZone.staticNames) do - local rawData, cat, ctry, parent = cfxMX.getStaticFromDCSbyName(aStaticName) + local rawData, cat, ctry, parent = cfxMX.getStaticFromDCSbyName(aStaticName) -- returns a UNIT data block if not rawData then - trigger.action.outText("Static Clone: no such group <"..aStaticName .. ">", 30) - + trigger.action.outText("Static Clone: no such group <"..aStaticName .. ">", 30) elseif rawData.name == aStaticName then - trigger.action.outText("Static Clone: suxess!!! <".. aStaticName ..">", 30) - + -- all good else trigger.action.outText("Static Clone: FAILED name check for <" .. aStaticName .. ">", 30) end - + local origID = rawData.unitId -- save original unit ID + rawData.CZorigID = origID + -- now use raw data to spawn and see if it works outabox - local theCat = cfxMX.catText2ID(cat) -- will be "static" + --local theCat = cfxMX.catText2ID(cat) -- will be "static" -- move origin rawData.x = rawData.x + zoneDelta.x @@ -317,20 +629,56 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) -- apply turning dcsCommon.rotateUnitData(rawData, spawnZone.turn, newCenter.x, newCenter.z) - -- make sure static name is unique --- cloneZones.uniqueNameGroupData(rawData) + -- make sure static name is unique and remember original rawData.name = dcsCommon.uuid(rawData.name) - rawData.unitID = nil -- simply forget, will be newly issued + rawData.unitId = cloneZones.uniqueID() + rawData.CZTargetID = rawData.unitId - -- see waht country we spawn for + -- see what country we spawn for ctry = cloneZones.resolveOwnership(spawnZone, ctry) + -- handle linkUnit if provided + if rawData.linkUnit then + --trigger.action.outText("has link to " .. rawData.linkUnit, 30) + local lU = cloneZones.resolveStaticLinkUnit(rawData.linkUnit) + --trigger.action.outText("resolved to " .. lU, 30) + rawData.linkUnit = lU + if not rawData.offsets then + rawData.offsets = {} + rawData.offsets.angle = 0 + rawData.offsets.x = 0 + rawData.offsets.y = 0 + --trigger.action.outText("clnZ: link required offset for " .. rawData.name, 30) + end + rawData.offsets.y = rawData.offsets.y - zoneDelta.z + rawData.offsets.x = rawData.offsets.x - zoneDelta.x + rawData.offsets.angle = rawData.offsets.angle + spawnZone.turn + rawData.linkOffset = true +-- trigger.action.outText("zone deltas are " .. zoneDelta.x .. ", " .. zoneDelta.y, 30) + end + local theStatic = coalition.addStaticObject(ctry, rawData) + local newStaticID = tonumber(theStatic:getID()) table.insert(spawnedStatics, theStatic) + -- we don't mix groups with units, so no lookup tables for + -- statics + if newStaticID == rawData.CZTargetID then +-- trigger.action.outText("Static ID OK: " .. newStaticID .. " for " .. rawData.name, 30) + else + trigger.action.outText("Static ID mismatch: " .. newStaticID .. " vs (target) " .. rawData.CZTargetID .. " for " .. rawData.name, 30) + end + cloneZones.unitXlate[origID] = newStaticID -- same as units + + cloneZones.invokeCallbacks(theZone, "did spawn static", theStatic) --]]-- - trigger.action.outText("Static spawn: spawned " .. aStaticName, 30) + if cloneZones.verbose then + trigger.action.outText("Static spawn: spawned " .. aStaticName, 30) + end end - + local args = {} + args.groups = spawnedGroups + args.statics = spawnedStatics + cloneZones.invokeCallbacks(theZone, "spawned", args) return spawnedGroups, spawnedStatics end @@ -381,6 +729,7 @@ function cloneZones.spawnWithCloner(theZone) -- pre-Wipe? if theZone.preWipe then cloneZones.despawnAll(theZone) + cloneZones.invokeCallbacks(theZone, "wiped", {}) end @@ -454,6 +803,7 @@ function cloneZones.hasLiveUnits(theZone) return false end +-- old code, deprecated function cloneZones.pollFlag(flagNum, method) -- we currently ignore method local num = trigger.misc.getUserFlag(flagNum) @@ -491,17 +841,24 @@ function cloneZones.update() 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 + -- empty handling + local isEmpty = cloneZones.countLiveUnits(aZone) < 1 and aZone.hasClones + if isEmpty then + -- see if we need to bang a flag + if aZone.emptyFlag then cloneZones.pollFlag(aZone.emptyFlag) - aZone.hasClones = false + end + + if aZone.emptyBangFlag then + cfxZones.pollFlag(aZone.emptyBangFlag, aZone.method) end + -- invoke callbacks + cloneZones.invokeCallbacks(aZone, "empty", {}) + + -- prevent isEmpty next pass + aZone.hasClones = false end - end end @@ -571,8 +928,25 @@ function cloneZones.start() 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 +end + +--[[-- callback testing +czcb = {} +function czcb.callback(theZone, reason, args) + trigger.action.outText("clone CB: " .. theZone.name .. " with " .. reason, 30) +end +cloneZones.addCallback(czcb.callback) +--]]-- + +--[[-- + to resolve tasks + + - AFAC + - FAC Assign group + - set freq for unit +--]]-- \ No newline at end of file diff --git a/modules/countDown.lua b/modules/countDown.lua index 5d88be4..2f391de 100644 --- a/modules/countDown.lua +++ b/modules/countDown.lua @@ -15,6 +15,7 @@ countDown.requiredLibs = { 1.1.0 - Lua interface: callbacks - corrected verbose (erroneously always suppressed) - triggerFlag --> triggerCountFlag + 1.1.1 - corrected bug in invokeCallback --]]-- @@ -56,7 +57,7 @@ function countDown.invokeCallbacks(theZone, val, tminus, zero, belowZero, loopin -- invoke anyone who wants to know that a group -- of people was rescued. - for idx, cb in pairs(csarManager.csarCompleteCB) do + for idx, cb in pairs(countDown.callbacks) do cb(theZone, val, tminus, zero, belowZero, looping) end end @@ -94,7 +95,7 @@ function countDown.createCountDownWithZone(theZone) end if theZone.triggerCountFlag then - theZone.lastTriggerValue = trigger.misc.getUserFlag(theZone.triggerCountFlag) -- save last value + theZone.lastCountTriggerValue = trigger.misc.getUserFlag(theZone.triggerCountFlag) -- save last value end -- zero! bang @@ -184,13 +185,13 @@ function countDown.update() -- make sure to re-start before reading time limit if aZone.triggerCountFlag then local currTriggerVal = trigger.misc.getUserFlag(aZone.triggerCountFlag) - if currTriggerVal ~= aZone.lastTriggerValue + if currTriggerVal ~= aZone.lastCountTriggerValue then if countDown.verbose then trigger.action.outText("+++cntD: triggered on in?", 30) end countDown.isTriggered(aZone) - aZone.lastTriggerValue = trigger.misc.getUserFlag(aZone.triggerCountFlag) -- save last value + aZone.lastCountTriggerValue = trigger.misc.getUserFlag(aZone.triggerCountFlag) -- save last value end end end diff --git a/modules/dcsCommon.lua b/modules/dcsCommon.lua index 925e774..1fd5048 100644 --- a/modules/dcsCommon.lua +++ b/modules/dcsCommon.lua @@ -1,5 +1,5 @@ dcsCommon = {} -dcsCommon.version = "2.5.4" +dcsCommon.version = "2.5.5" --[[-- VERSION HISTORY 2.2.6 - compassPositionOfARelativeToB - clockPositionOfARelativeToB @@ -62,6 +62,9 @@ dcsCommon.version = "2.5.4" - removed forced error in failed pickRandom 2.5.4 - rotateUnitData() - randomBetween() + 2.5.5 - stringStartsWithDigit() + - stringStartsWithLetter() + - stringIsPositiveNumber() --]]-- @@ -1700,7 +1703,31 @@ dcsCommon.version = "2.5.4" return trimmedArray end - -- same as cfxZones, this is the commonly used, may need to remove from zones + function dcsCommon.stringIsPositiveNumber(theString) + -- only full integer positive numbers supported + if not theString then return false end +-- if theString == "" then return false end + for i = 1, #theString do + local c = theString:sub(i,i) + if c < "0" or c > "9" then return false end + end + return true + end + + function dcsCommon.stringStartsWithDigit(theString) + if #theString < 1 then return false end + local c = string.sub(theString, 1, 1) + return c >= "0" and c <= "9" + end + + function dcsCommon.stringStartsWithLetter(theString) + if #theString < 1 then return false end + local c = string.sub(theString, 1, 1) + if c >= "a" and c <= "z" then return true end + if c >= "A" and c <= "Z" then return true end + return false + end + function dcsCommon.stringStartsWith(theString, thePrefix) return theString:find(thePrefix) == 1 end @@ -2109,8 +2136,8 @@ end function dcsCommon.init() cbID = 0 dcsCommon.uuIdent = 0 - if (dcsCommon.verbose) then - trigger.action.outText("dcsCommon v" .. dcsCommon.version .. " loaded successfully", 10) + if (dcsCommon.verbose) or true then + trigger.action.outText("dcsCommon v" .. dcsCommon.version .. " loaded", 10) end end diff --git a/modules/delayFlags.lua b/modules/delayFlags.lua index 8e49cb2..0629083 100644 --- a/modules/delayFlags.lua +++ b/modules/delayFlags.lua @@ -1,5 +1,5 @@ delayFlag = {} -delayFlag.version = "1.0.2" +delayFlag.version = "1.0.4" delayFlag.verbose = false delayFlag.requiredLibs = { "dcsCommon", -- always @@ -20,7 +20,9 @@ delayFlag.flags = {} - using cfxZones for polling - removed pollFlag 1.0.3 - bug fix for config zone name - - removed message attribute, moved to own module + - removed message attribute, moved to own module + - triggerFlag --> triggerDelayFlag + 1.0.4 - startDelay --]]-- @@ -55,15 +57,19 @@ function delayFlag.createTimerWithZone(theZone) -- trigger flag if cfxZones.hasProperty(theZone, "f?") then - theZone.triggerFlag = cfxZones.getStringFromZoneProperty(theZone, "f?", "none") + theZone.triggerDelayFlag = cfxZones.getStringFromZoneProperty(theZone, "f?", "none") end if cfxZones.hasProperty(theZone, "in?") then - theZone.triggerFlag = cfxZones.getStringFromZoneProperty(theZone, "in?", "none") + theZone.triggerDelayFlag = cfxZones.getStringFromZoneProperty(theZone, "in?", "none") end - if theZone.triggerFlag then - theZone.lastTriggerValue = trigger.misc.getUserFlag(theZone.triggerFlag) -- save last value + if cfxZones.hasProperty(theZone, "startDelay?") then + theZone.triggerDelayFlag = cfxZones.getStringFromZoneProperty(theZone, "startDelay?", "none") + end + + if theZone.triggerDelayFlag then + theZone.lastDelayTriggerValue = trigger.misc.getUserFlag(theZone.triggerDelayFlag) -- save last value end @@ -79,10 +85,6 @@ function delayFlag.createTimerWithZone(theZone) theZone.onStart = cfxZones.getBoolFromZoneProperty(theZone, "onStart", false) end - -- message - if cfxZones.hasProperty(theZone, "message") then - theZone.myMessage = cfxZones.getStringZoneProperty(theZone, "message", "") - end -- init theZone.running = false @@ -128,9 +130,9 @@ function delayFlag.update() 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 + if aZone.triggerDelayFlag then + local currTriggerVal = trigger.misc.getUserFlag(aZone.triggerDelayFlag) + if currTriggerVal ~= aZone.lastDelayTriggerValue then if delayFlag.verbose then if aZone.running then @@ -140,7 +142,7 @@ function delayFlag.update() end end delayFlag.startDelay(aZone) -- we restart even if running - aZone.lastTriggerValue = currTriggerVal + aZone.lastDelayTriggerValue = currTriggerVal end end @@ -152,9 +154,9 @@ function delayFlag.update() -- poll flag cfxZones.pollFlag(aZone.outFlag, aZone.method) -- say message - if aZone.myMessage then - trigger.action.outText(aZone.myMessage, 30) - end + --if aZone.myMessage then + -- trigger.action.outText(aZone.myMessage, 30) + --end end end diff --git a/modules/messenger.lua b/modules/messenger.lua index a326800..34a8257 100644 --- a/modules/messenger.lua +++ b/modules/messenger.lua @@ -1,5 +1,5 @@ messenger = {} -messenger.version = "1.0.0" +messenger.version = "1.0.1" messenger.verbose = false messenger.requiredLibs = { "dcsCommon", -- always @@ -9,6 +9,8 @@ messenger.messengers = {} --[[-- Version History 1.0.0 - initial version + 1.0.1 - messageOut? synonym + - spelling types in about --]]-- @@ -53,6 +55,10 @@ function messenger.createMessengerDownWithZone(theZone) theZone.triggerMessagerFlag = cfxZones.getStringFromZoneProperty(theZone, "in?", "none") end + if cfxZones.hasProperty(theZone, "messageOut?") then + theZone.triggerMessagerFlag = cfxZones.getStringFromZoneProperty(theZone, "messageOut?", "none") + end + if theZone.triggerMessagerFlag then theZone.lastMessageTriggerValue = trigger.misc.getUserFlag(theZone.triggerMessagerFlag) -- save last value end @@ -125,10 +131,10 @@ end function messenger.start() -- lib check if not dcsCommon.libCheck then - trigger.action.outText("cfx Count Down requires dcsCommon", 30) + trigger.action.outText("cfx Messenger requires dcsCommon", 30) return false end - if not dcsCommon.libCheck("cfx Count Down", messenger.requiredLibs) then + if not dcsCommon.libCheck("cfx Messenger", messenger.requiredLibs) then return false end diff --git a/modules/pulseFlags.lua b/modules/pulseFlags.lua index 49cea4c..47e7e77 100644 --- a/modules/pulseFlags.lua +++ b/modules/pulseFlags.lua @@ -1,5 +1,5 @@ pulseFlags = {} -pulseFlags.version = "1.0.2" +pulseFlags.version = "1.0.3" pulseFlags.verbose = false pulseFlags.requiredLibs = { "dcsCommon", -- always @@ -14,6 +14,10 @@ pulseFlags.requiredLibs = { - 1.0.0 Initial version - 1.0.1 pause behavior debugged - 1.0.2 zero pulse optional initial pulse suppress + - 1.0.3 pollFlag switched to cfxZones + uses randomDelayFromPositiveRange + flag! now is string + WARNING: still needs full alphaNum flag upgrade --]]-- @@ -28,7 +32,7 @@ end -- function pulseFlags.createPulseWithZone(theZone) - theZone.flag = cfxZones.getNumberFromZoneProperty(theZone, "flag!", -1) -- the flag to pulse + theZone.flag = cfxZones.getStringFromZoneProperty(theZone, "flag!", -1) -- the flag to pulse -- time can be number, or number-number range theZone.minTime, theZone.time = cfxZones.getPositiveRangeFromZoneProperty(theZone, "time", 1) @@ -68,42 +72,6 @@ end -- -- update -- -function pulseFlags.pollFlag(theFlag, method) - if pulseFlags.verbose then - trigger.action.outText("+++PulF: 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("+++PulF: unknown method <" .. method .. "> - using 'on'", 30) - end - -- default: on. - trigger.action.setUserFlag(theFlag, 1) - end - - local newVal = trigger.misc.getUserFlag(theFlag) - if pulseFlags.verbose then - trigger.action.outText("+++PulF: flag <" .. theFlag .. "> changed from " .. currVal .. " to " .. newVal, 30) - end -end function pulseFlags.doPulse(args) @@ -118,7 +86,7 @@ function pulseFlags.doPulse(args) -- do a poll on flags -- first, we only do an initial pulse if zeroPulse is set if theZone.hasPulsed or theZone.zeroPulse then - pulseFlags.pollFlag(theZone.flag, theZone.method) + cfxZones.pollFlag(theZone.flag, theZone.method, theZone) -- decrease count if theZone.pulses > 0 then @@ -149,6 +117,7 @@ function pulseFlags.doPulse(args) theZone.hasPulsed = true -- we are past initial pulse -- if we get here, schedule next pulse + --[[-- local delay = theZone.time if theZone.minTime > 0 and theZone.minTime < delay then -- we want a randomized from time from minTime .. delay @@ -156,6 +125,8 @@ function pulseFlags.doPulse(args) varPart = dcsCommon.smallRandom(varPart) - 1 delay = theZone.minTime + varPart end + --]]-- + local delay = cfxZones.randomDelayFromPositiveRange(theZone.minTime, theZone.time) --trigger.action.outText("***PulF: pulse <" .. theZone.name .. "> scheduled in ".. delay .."!", 30) diff --git a/modules/raiseFlag.lua b/modules/raiseFlag.lua new file mode 100644 index 0000000..3705c40 --- /dev/null +++ b/modules/raiseFlag.lua @@ -0,0 +1,137 @@ +raiseFlag = {} +raiseFlag.version = "1.0.0" +raiseFlag.verbose = false +raiseFlag.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course +} +raiseFlag.flags = {} +--[[-- + Raise A Flag module -- (c) 2022 by Christian Franz and cf/x AG + + Version History + 1.0.0 - initial release +--]]-- +function raiseFlag.addRaiseFlag(theZone) + table.insert(raiseFlag.flags, theZone) +end + +function raiseFlag.getRaiseFlagByName(aName) + for idx, aZone in pairs(raiseFlag.flags) do + if aName == aZone.name then return aZone end + end + if raiseFlag.verbose then + trigger.action.outText("+++rFlg: no raiseFlag with name <" .. aName ..">", 30) + end + + return nil +end + +-- +-- read attributes +-- +function raiseFlag.createRaiseFlagWithZone(theZone) + -- get flag from faiseFlag itself + theZone.raiseFlag = cfxZones.getStringFromZoneProperty(theZone, "raiseFlag", "") -- the flag to raise + + theZone.flagValue = cfxZones.getNumberFromZoneProperty(theZone, "value", 1) -- value to set to + + theZone.minAfterTime, theZone.maxAfterTime = cfxZones.getPositiveRangeFromZoneProperty(theZone, "afterTime", -1) + + if cfxZones.hasProperty(theZone, "stopFlag?") then + theZone.triggerStopFlag = cfxZones.getStringFromZoneProperty(theZone, "stopFlag?", "none") + theZone.lastTriggerStopValue = cfxZones.getFlagValue(theZone.triggerStopFlag, theZone) -- save last value + end + + theZone.scheduleID = nil + theZone.raiseStopped = false + + -- now simply schedule for invocation + local args = {} + args.theZone = theZone + if theZone.minAfterTime < 1 then + timer.scheduleFunction(raiseFlag.triggered, args, timer.getTime() + 0.5) + else + local delay = cfxZones.randomDelayFromPositiveRange(theZone.minAfterTime, theZone.maxAfterTime) + timer.scheduleFunction(raiseFlag.triggered, args, timer.getTime() + delay) + end +end + +function raiseFlag.triggered(args) + local theZone = args.theZone + if theZone.raiseStopped then return end + -- if we get here, we aren't stopped and do the flag pull + cfxZones.setFlagValue(theZone.raiseFlag, theZone.flagValue, theZone) +end + +-- +-- update +-- +function raiseFlag.update() + -- call me in a second to poll triggers + timer.scheduleFunction(raiseFlag.update, {}, timer.getTime() + 1) + + for idx, aZone in pairs(raiseFlag.flags) do + -- make sure to re-start before reading time limit + if aZone.triggerStopFlag then + local currTriggerVal = cfxZones.getFlagValue(aZone.triggerStopFlag, theZone) + if currTriggerVal ~= aZone.lastTriggerStopValue + then + theZone.raiseStopped = true -- we are done, no flag! + end + end + end +end + +-- +-- config & go! +-- + +function raiseFlag.readConfigZone() + local theZone = cfxZones.getZoneByName("raiseFlagConfig") + if not theZone then + if raiseFlag.verbose then + trigger.action.outText("+++rFlg: NO config zone!", 30) + end + return + end + + raiseFlag.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + + if raiseFlag.verbose then + trigger.action.outText("+++rFlg: read config", 30) + end +end + +function raiseFlag.start() + -- lib check + if not dcsCommon.libCheck then + trigger.action.outText("cfx raise flag requires dcsCommon", 30) + return false + end + if not dcsCommon.libCheck("cfx Count Down", raiseFlag.requiredLibs) then + return false + end + + -- read config + raiseFlag.readConfigZone() + + -- process cloner Zones + local attrZones = cfxZones.getZonesWithAttributeNamed("raiseFlag") + for k, aZone in pairs(attrZones) do + raiseFlag.createRaiseFlagWithZone(aZone) -- process attributes + raiseFlag.addRaiseFlag(aZone) -- add to list + end + + -- start update + raiseFlag.update() + + trigger.action.outText("cfx raiseFlag v" .. raiseFlag.version .. " started.", 30) + return true +end + +-- let's go! +if not raiseFlag.start() then + trigger.action.outText("cfx Raise Flag aborted: missing libraries", 30) + raiseFlag = nil +end \ No newline at end of file diff --git a/tutorial & demo missions/demo - Clone Relations.miz b/tutorial & demo missions/demo - Clone Relations.miz new file mode 100644 index 0000000..5caf558 Binary files /dev/null and b/tutorial & demo missions/demo - Clone Relations.miz differ diff --git a/tutorial & demo missions/demo - Pulsing Fun.miz b/tutorial & demo missions/demo - Pulsing Fun.miz index 2b7ecf9..f6d656c 100644 Binary files a/tutorial & demo missions/demo - Pulsing Fun.miz and b/tutorial & demo missions/demo - Pulsing Fun.miz differ diff --git a/tutorial & demo missions/demo - flag fun.miz b/tutorial & demo missions/demo - flag fun.miz new file mode 100644 index 0000000..db39f1d Binary files /dev/null and b/tutorial & demo missions/demo - flag fun.miz differ diff --git a/tutorial & demo missions/demo - object destruct detection.miz b/tutorial & demo missions/demo - object destruct detection.miz index a3354ed..b937f67 100644 Binary files a/tutorial & demo missions/demo - object destruct detection.miz and b/tutorial & demo missions/demo - object destruct detection.miz differ