diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index e637003..08f61ff 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 index 285ad2e..def86ca 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/airfield.lua b/modules/airfield.lua index df02188..7dd5151 100644 --- a/modules/airfield.lua +++ b/modules/airfield.lua @@ -1,5 +1,5 @@ airfield = {} -airfield.version = "1.0.0" +airfield.version = "1.1.0" airfield.requiredLibs = { "dcsCommon", "cfxZones", @@ -15,15 +15,21 @@ airfield.farps = false Version History 1.0.0 - initial release - + 1.1.0 - added 'fixed' attribute + - added 'farps' attribute to individual zones + - allow zone.local farps designation + - always checks farp cap events + - added verbosity --]]-- -- -- setting up airfield -- function airfield.createAirFieldFromZone(theZone) + theZone.farps = theZone:getBoolFromZoneProperty("farps", false) + local filterCat = 0 - if airfield.farps then filterCat = {0, 1} end -- bases and farps + if (airfield.farps or theZone.farps) then filterCat = {0, 1} end -- bases and farps local p = theZone:getPoint() local theBase = dcsCommon.getClosestAirbaseTo(p, filterCat) theZone.airfield = theBase @@ -72,12 +78,26 @@ function airfield.createAirFieldFromZone(theZone) trigger.action.setUserFlag(theZone.ownedBy, theZone.owner) end + -- if fixed attribute, we switch to that color and keep it fixed. + -- can be overridden by either makeXX or autoCap. + if theZone:hasProperty("fixed") then + local theFixed = theZone:getCoalitionFromZoneProperty("fixed") + local theAirfield = theZone.airfield + airfield.assumeControl(theZone) -- turn off capturable + theAirfield:setCoalition(theFixed) + theZone.owner = theFixed + end + -- index by name, and warn if duplicate associate if airfield.myAirfields[theZone.afName] then trigger.action.outText("+++airF: WARNING - zone <" .. theZone:getName() .. "> redefines airfield <" .. theZone.afName .. ">, discarded!", 30) else airfield.myAirfields[theZone.afName] = theZone end + + if theZone.verbose or airfield.verbose then + trigger.action.outText("+++airF: airfield zone <" .. theZone.name .. "> associates with <" .. theZone.afName .. ">, current owner is <" .. theZone.owner .. ">", 30) + end end function airfield.assumeControl(theZone) @@ -137,14 +157,14 @@ function airfield:onEvent(event) local desc = theBase:getDesc() local bName = theBase:getName() local cat = desc.category -- never get cat directly! - if cat == 1 then +--[[-- if cat == 1 then if not airfield.farps then if airfield.verbose then trigger.action.outText("+++airF: ignored cap event for FARP <" .. bName .. ">", 30) end return end - end + end --]] if airfield.verbose then trigger.action.outText("+++airF: cap event for <" .. bName .. ">, cat = (" .. cat .. ")", 30) end diff --git a/modules/cfxHeloTroops.lua b/modules/cfxHeloTroops.lua index 39250f3..e9dd6df 100644 --- a/modules/cfxHeloTroops.lua +++ b/modules/cfxHeloTroops.lua @@ -1,9 +1,10 @@ cfxHeloTroops = {} -cfxHeloTroops.version = "2.4.1" +cfxHeloTroops.version = "3.0.0" cfxHeloTroops.verbose = false cfxHeloTroops.autoDrop = true cfxHeloTroops.autoPickup = false cfxHeloTroops.pickupRange = 100 -- meters +cfxHeloTroops.requestRange = 500 -- meters -- --[[-- VERSION HISTORY @@ -32,7 +33,11 @@ cfxHeloTroops.pickupRange = 100 -- meters - removed restriction to only apply to helicopters in anticipation of the C-130 Hercules appearing in the game 2.4.1 - new actionSound attribute, sound plays to group whenever troops have boarded or disembarked - + 3.0.0 - added requestable cloner support + - harmonized spawning invocations across cloners and spawners + - dmlZones + - requestRange attribute + --]]-- -- -- cfxHeloTroops -- a module to pick up and drop infantry. @@ -158,13 +163,11 @@ function cfxHeloTroops.heloLanded(theUnit) cfxHeloTroops.setCommsMenu(conf.unit) end - -- -- -- Helo took off -- -- - function cfxHeloTroops.heloDeparted(theUnit) if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return end @@ -206,17 +209,14 @@ function cfxHeloTroops.cleanHelo(theUnit) end end end - conf.troopsOnBoard = {} end function cfxHeloTroops.heloCrashed(theUnit) if not dcsCommon.isTroopCarrier(theUnit, cfxHeloTroops.troopCarriers) then return end - -- clean up cfxHeloTroops.cleanHelo(theUnit) - end -- @@ -252,7 +252,6 @@ function cfxHeloTroops.removeComms(theUnit) conf.id = id conf.unit = theUnit - cfxHeloTroops.removeCommsFromConfig(conf) end @@ -371,16 +370,15 @@ function cfxHeloTroops.addGroundMenu(conf) return end - -- case 2A: no troops aboard, and spawners in range - -- that are requestable + -- case 2A: no troops aboard, and requestable spawners/cloners in range local p = conf.unit:getPosition().p local mySide = conf.unit:getCoalition() - - if cfxSpawnZones then - -- only if SpawnZones is implemented - local availableSpawnersRaw = cfxSpawnZones.getRequestableSpawnersInRange(p, 500, mySide) - -- DONE: requestable spawners must check for troop compatibility - local availableSpawners = {} + + -- collect available spawn zones + local availableSpawners = {} + if cfxSpawnZones then -- only if SpawnZones is implemented + local availableSpawnersRaw = cfxSpawnZones.getRequestableSpawnersInRange(p, cfxHeloTroops.requestRange, mySide) + for idx, aSpawner in pairs(availableSpawnersRaw) do -- filter all spawners that spawn "illegal" troops local theTypes = aSpawner.types @@ -403,26 +401,55 @@ function cfxHeloTroops.addGroundMenu(conf) table.insert(availableSpawners, aSpawner) end end - - local numSpawners = #availableSpawners - if numSpawners > 5 then numSpawners = 5 end - while numSpawners > 0 do - -- for each spawner in range, create a - -- spawn menu item - local spawner = availableSpawners[numSpawners] - local theName = spawner.baseName - local comm = "Request <" .. theName .. "> troops for transport" -- .. math.floor(aTeam.dist) .. "m away" - local theCommand = missionCommands.addCommandForGroup( - conf.id, - comm, - conf.myMainMenu, - cfxHeloTroops.redirectSpawnGroup, - {conf, spawner} - ) - table.insert(conf.myCommands, theCommand) - numSpawners = numSpawners - 1 - end + end + -- collect available clone zones + if cloneZones then + local availableSpawnersRaw = cloneZones.getRequestableClonersInRange(p, cfxHeloTroops.requestRange, mySide) + for idx, aSpawner in pairs(availableSpawnersRaw) do + -- filter all spawners that spawn "illegal" troops or have none + local theTypes = aSpawner.allTypes + local allLegal = true + local numTypes = dcsCommon.getSizeOfTable(theTypes) + if numTypes > 0 then + for aType, cnt in pairs(theTypes) do + if cfxHeloTroops.legalTroops then + if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, aType) then + allLegal = false + end + else + if not dcsCommon.typeIsInfantry(aType) then + allLegal = false + end + end + end + else + allegal = false + end + + if allLegal then + table.insert(availableSpawners, aSpawner) + end + end + end + + local numSpawners = #availableSpawners + if numSpawners > 5 then numSpawners = 5 end + while numSpawners > 0 do + -- for each spawner in range, create a + -- spawn menu item + local spawner = availableSpawners[numSpawners] + local theName = spawner.baseName + local comm = "Request <" .. theName .. "> troops for transport" -- .. math.floor(aTeam.dist) .. "m away" + local theCommand = missionCommands.addCommandForGroup( + conf.id, + comm, + conf.myMainMenu, + cfxHeloTroops.redirectSpawnGroup, + {conf, spawner} + ) + table.insert(conf.myCommands, theCommand) + numSpawners = numSpawners - 1 end -- case 2B: no troops aboard. see if there are troops around @@ -440,7 +467,6 @@ function cfxHeloTroops.addGroundMenu(conf) -- now limit the options to the five closest legal groups local numUnits = #unitsToLoad if numUnits > 5 then numUnits = 5 end - if numUnits < 1 then local theCommand = missionCommands.addCommandForGroup( conf.id, @@ -469,8 +495,6 @@ function cfxHeloTroops.addGroundMenu(conf) ) table.insert(conf.myCommands, theCommand) end - - end function cfxHeloTroops.filterTroopsByType(unitsToLoad) @@ -546,7 +570,6 @@ end -- -- Deploying Troops -- - function cfxHeloTroops.redirectDeployTroops(args) timer.scheduleFunction(cfxHeloTroops.doDeployTroops, args, timer.getTime() + 0.1) end @@ -626,8 +649,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf) trigger.action.outTextForGroup(conf.id, "+++ <" .. conf.troopsOnBoard.name .. "> revoke 'wait' orders, proceed with <".. orders .. ">", 30) end - local chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m ratius around choppa - --local theCoalition = theUnit:getCountry() -- make it choppers country + local chopperZone = cfxZones.createSimpleZone("choppa", p, 12) -- 12 m radius around choppa local theCoalition = theUnit:getGroup():getCoalition() -- make it choppers COALITION local theGroup, theData = cfxZones.createGroundUnitsInZoneForCoalition ( theCoalition, @@ -649,7 +671,7 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf) troop.destination = dest -- transfer target zone for attackzone oders cfxGroundTroops.addGroundTroopsToPool(troop) -- will schedule move orders trigger.action.outTextForGroup(conf.id, "<" .. theGroup:getName() .. "> have deployed to the ground with orders " .. orders .. "!", 30) - trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.actionSound) -- "Quest Snare 3.wav") + trigger.action.outSoundForGroup(conf.id, cfxHeloTroops.actionSound) -- see if this is tracked by a tracker, and pass them back so -- they can un-limbo if groupTracker then @@ -665,7 +687,6 @@ function cfxHeloTroops.deployTroopsFromHelicopter(conf) end end - -- -- Loading Troops -- @@ -755,7 +776,7 @@ end function cfxHeloTroops.doSpawnGroup(args) local conf = args[1] local theSpawner = args[2] - + -- NOTE: theSpawner can be of type cfxSpawnZone !!!OR!!! cfxCloneZones -- make sure cooldown on spawner has timed out, else -- notify that you have to wait local now = timer.getTime() @@ -765,13 +786,13 @@ function cfxHeloTroops.doSpawnGroup(args) return end - cfxSpawnZones.spawnWithSpawner(theSpawner) + --cfxSpawnZones.spawnWithSpawner(theSpawner) -- old code + theSpawner.spawnWithSpawner(theSpawner) -- can be both spawner and cloner trigger.action.outTextForGroup(conf.id, "Deploying <" .. theSpawner.baseName .. "> now...", 30) -- reset all comms so we can include new troops -- into load menu timer.scheduleFunction(cfxHeloTroops.delayedCommsResetForUnit, {conf.unit, "ignore"}, now + 1.0) - end -- @@ -859,14 +880,13 @@ function cfxHeloTroops.readConfigZone() -- note: must match exactly!!!! local theZone = cfxZones.getZoneByName("heloTroopsConfig") if not theZone then - trigger.action.outText("+++heloT: no config zone!", 30) theZone = cfxZones.createSimpleZone("heloTroopsConfig") end - cfxHeloTroops.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + cfxHeloTroops.verbose = theZone:getBoolFromZoneProperty("verbose", false) - if cfxZones.hasProperty(theZone, "legalTroops") then - local theTypesString = cfxZones.getStringFromZoneProperty(theZone, "legalTroops", "") + if theZone:hasProperty("legalTroops") then + local theTypesString = theZone:getStringFromZoneProperty("legalTroops", "") local unitTypes = dcsCommon.splitString(aSpawner.types, ",") if #unitTypes < 1 then unitTypes = {"Soldier AK", "Infantry AK", "Infantry AK ver2", "Infantry AK ver3", "Infantry AK Ins", "Soldier M249", "Soldier M4 GRG", "Soldier M4", "Soldier RPG", "Paratrooper AKS-74", "Paratrooper RPG-16", "Stinger comm dsr", "Stinger comm", "Soldier stinger", "SA-18 Igla-S comm", "SA-18 Igla-S manpad", "Igla manpad INS", "SA-18 Igla comm", "SA-18 Igla manpad",} -- default @@ -876,18 +896,19 @@ function cfxHeloTroops.readConfigZone() cfxHeloTroops.legalTroops = unitTypes end - cfxHeloTroops.troopWeight = cfxZones.getNumberFromZoneProperty(theZone, "troopWeight", 100) -- kg average weight per trooper + cfxHeloTroops.troopWeight = theZone:getNumberFromZoneProperty("troopWeight", 100) -- kg average weight per trooper - cfxHeloTroops.autoDrop = cfxZones.getBoolFromZoneProperty(theZone, "autoDrop", false) - cfxHeloTroops.autoPickup = cfxZones.getBoolFromZoneProperty(theZone, "autoPickup", false) - cfxHeloTroops.pickupRange = cfxZones.getNumberFromZoneProperty(theZone, "pickupRange", 100) - cfxHeloTroops.combatDropScore = cfxZones.getNumberFromZoneProperty(theZone, "combatDropScore", 200) + cfxHeloTroops.autoDrop = theZone:getBoolFromZoneProperty("autoDrop", false) + cfxHeloTroops.autoPickup = theZone:getBoolFromZoneProperty("autoPickup", false) + cfxHeloTroops.pickupRange = theZone:getNumberFromZoneProperty("pickupRange", 100) + cfxHeloTroops.combatDropScore = theZone:getNumberFromZoneProperty( "combatDropScore", 200) - cfxHeloTroops.actionSound = cfxZones.getStringFromZoneProperty(theZone, "actionSound", "Quest Snare 3.wav") + cfxHeloTroops.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav") + cfxHeloTroops.requestRange = theZone:getNumberFromZoneProperty("requestRange", 500) -- add own troop carriers - if cfxZones.hasProperty(theZone, "troopCarriers") then - local tc = cfxZones.getStringFromZoneProperty(theZone, "troopCarriers", "UH-1D") + if theZone:hasProperty("troopCarriers") then + local tc = theZone:getStringFromZoneProperty("troopCarriers", "UH-1D") tc = dcsCommon.splitString(tc, ",") cfxHeloTroops.troopCarriers = dcsCommon.trimArray(tc) end diff --git a/modules/cfxSpawnZones.lua b/modules/cfxSpawnZones.lua index 7704949..6cfd73c 100644 --- a/modules/cfxSpawnZones.lua +++ b/modules/cfxSpawnZones.lua @@ -1,5 +1,5 @@ cfxSpawnZones = {} -cfxSpawnZones.version = "1.7.5" +cfxSpawnZones.version = "2.0.0" cfxSpawnZones.requiredLibs = { "dcsCommon", -- common is of course needed for everything -- pretty stupid to check for this since we @@ -68,27 +68,10 @@ cfxSpawnZones.spawnedGroups = {} 1.7.4 - wait-attackZone fixes 1.7.5 - improved verbosity on spawning - getRequestableSpawnersInRange() ignores height for distance - - - - types - type strings, comma separated - see here: https://github.com/mrSkortch/DCS-miscScripts/tree/master/ObjectDB - - - country - defaults to 2 (usa) -- see here https://wiki.hoggitworld.com/view/DCS_enum_country - some important: 0 = Russia, 2 = US, 82 = UN neutral - country is converted to coalition and then assigned to - Joint Task Force upon spawn - - - formation - default is circle_out; other formations are - - line - left lo right (west-east) facing north - - line_V - vertical line, facing north - - chevron - west-east, point growing to north - - scattered, random - - circle, circle_forward (all fact north) - - circle-in (all face in) - - circle-out (all face out) - - grid, square, rect arrayed in optimal grid - - 2deep, 2cols two columns, deep - - 2wide 2 columns wide (2 deep) + 2.0.0 - dmlZones + - moved "types" to spawner + - baseName defaults to zone name, as it is safe for naming + - spawnWithSpawner direct link in spawner to spawnZones --]]-- cfxSpawnZones.allSpawners = {} @@ -115,103 +98,101 @@ function cfxSpawnZones.createSpawner(inZone) local theSpawner = {} theSpawner.zone = inZone theSpawner.name = inZone.name - + theSpawner.spawnWithSpawner = cfxSpawnZones.spawnWithSpawner -- interface to groupTracker -- WARNING: attaches to ZONE, not spawner object - if cfxZones.hasProperty(inZone, "trackWith:") then - inZone.trackWith = cfxZones.getStringFromZoneProperty(inZone, "trackWith:", "") + if inZone:hasProperty("trackWith:") then + inZone.trackWith = inZone:getStringFromZoneProperty("trackWith:", "") end -- interface to delicates - if cfxZones.hasProperty(inZone, "useDelicates") then - theSpawner.delicateName = dcsCommon.trim(cfxZones.getStringFromZoneProperty(inZone, "useDelicates", "")) + if inZone:hasProperty("useDelicates") then + theSpawner.delicateName = dcsCommon.trim(inZone:getStringFromZoneProperty("useDelicates", "")) if theSpawner.delicateName == "*" then theSpawner.delicateName = inZone.name end end - -- connect with ME if a trigger flag is given - if cfxZones.hasProperty(inZone, "f?") then - theSpawner.triggerFlag = cfxZones.getStringFromZoneProperty(inZone, "f?", "none") + if inZone:hasProperty("f?") then + theSpawner.triggerFlag = inZone:getStringFromZoneProperty("f?", "none") + theSpawner.lastTriggerValue = trigger.misc.getUserFlag(theSpawner.triggerFlag) + elseif inZone:hasProperty("spawn?") then + theSpawner.triggerFlag = inZone:getStringFromZoneProperty("spawn?", "none") + theSpawner.lastTriggerValue = trigger.misc.getUserFlag(theSpawner.triggerFlag) + elseif inZone:hasProperty("spawnUnits?") then + theSpawner.triggerFlag = inZone:getStringFromZoneProperty( "spawnObject?", "none") 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") + if inZone:hasProperty("activate?") then + theSpawner.activateFlag = inZone:getStringFromZoneProperty( "activate?", "none") theSpawner.lastActivateValue = trigger.misc.getUserFlag(theSpawner.activateFlag) end - if cfxZones.hasProperty(inZone, "pause?") then - theSpawner.pauseFlag = cfxZones.getStringFromZoneProperty(inZone, "pause?", "none") + if inZone:hasProperty("pause?") then + theSpawner.pauseFlag = inZone:getStringFromZoneProperty("pause?", "none") theSpawner.lastPauseValue = trigger.misc.getUserFlag(theSpawner.pauseFlag) end - theSpawner.types = cfxZones.getZoneProperty(inZone, "types") - --theSpawner.owner = cfxZones.getCoalitionFromZoneProperty(inZone, "owner", 0) - -- synthesize types * typeMult - local n = cfxZones.getNumberFromZoneProperty(inZone, "typeMult", 1) - local repeater = "" - if n < 1 then n = 1 end - while n > 1 do - repeater = repeater .. "," .. theSpawner.types - n = n - 1 + if inZone:hasProperty("types") then + theSpawner.types = inZone:getStringFromZoneProperty("types", "Soldier M4") + else + theSpawner.types = inZone:getStringFromZoneProperty("spawner", "Soldier M4") + end + -- synthesize types * typeMult + if inZone:hasProperty("typeMult") then + local n = inZone:getNumberFromZoneProperty("typeMult", 1) + local repeater = "" + if n < 1 then n = 1 end + while n > 1 do + repeater = repeater .. "," .. theSpawner.types + n = n - 1 + end + theSpawner.types = theSpawner.types .. repeater end - theSpawner.types = theSpawner.types .. repeater - theSpawner.country = cfxZones.getNumberFromZoneProperty(inZone, "country", 0) -- coalition2county(theSpawner.owner) - theSpawner.masterZoneName = cfxZones.getStringFromZoneProperty(inZone, "masterOwner", "") - if theSpawner.masterZoneName == "" then theSpawner.masterZoneName = nil end + theSpawner.country = inZone:getNumberFromZoneProperty("country", 0) + if inZone:hasProperty("masterOwner") then + theSpawner.masterZoneName = inZone:getStringFromZoneProperty("masterOwner", "") + if theSpawner.masterZoneName == "" then theSpawner.masterZoneName = nil end + end theSpawner.rawOwner = coalition.getCountryCoalition(theSpawner.country) - --theSpawner.baseName = cfxZones.getZoneProperty(inZone, "baseName") - theSpawner.baseName = cfxZones.getStringFromZoneProperty(inZone, "baseName", dcsCommon.uuid("SpwnDflt")) + -- theSpawner.baseName = inZone:getStringFromZoneProperty("baseName", dcsCommon.uuid("SpwnDflt")) + theSpawner.baseName = inZone:getStringFromZoneProperty("baseName", "*") theSpawner.baseName = dcsCommon.trim(theSpawner.baseName) if theSpawner.baseName == "*" then theSpawner.baseName = inZone.name -- convenience shortcut end - theSpawner.cooldown = cfxZones.getNumberFromZoneProperty(inZone, "cooldown", 60) - theSpawner.autoRemove = cfxZones.getBoolFromZoneProperty(inZone, "autoRemove", false) - theSpawner.lastSpawnTimeStamp = -10000 -- just init so it will always work - theSpawner.heading = cfxZones.getNumberFromZoneProperty(inZone, "heading", 0) - --trigger.action.outText("+++spwn: zone " .. inZone.name .. " owner " .. theSpawner.owner " --> ctry " .. theSpawner.country, 30) - + theSpawner.cooldown = inZone:getNumberFromZoneProperty("cooldown", 60) + theSpawner.autoRemove = inZone:getBoolFromZoneProperty("autoRemove", false) + theSpawner.lastSpawnTimeStamp = -10000 -- init so it will always work + theSpawner.heading = inZone:getNumberFromZoneProperty("heading", 0) theSpawner.cdTimer = 0 -- used for cooldown. if timer.getTime < this value, don't spawn theSpawner.cdStarted = false -- used to initiate cooldown when theSpawn disappears theSpawner.count = 1 -- used to create names, and count how many groups created theSpawner.theSpawn = nil -- link to last spawned group - theSpawner.formation = "circle_out" - theSpawner.formation = cfxZones.getStringFromZoneProperty(inZone, "formation", "circle_out") - theSpawner.paused = cfxZones.getBoolFromZoneProperty(inZone, "paused", false) + theSpawner.formation = inZone:getStringFromZoneProperty("formation", "circle_out") + theSpawner.paused = inZone:getBoolFromZoneProperty("paused", false) -- orders are always converted to all lower case - theSpawner.orders = cfxZones.getStringFromZoneProperty(inZone, "orders", "guard"):lower() + theSpawner.orders = inZone:getStringFromZoneProperty("orders", "guard"):lower() -- used to assign orders, default is 'guard', use "laze" to make them laze targets. can be 'wait-' which may auto-convert to 'guard' after pick-up by helo, to be handled outside. -- use "train" to tell them to HOLD WEAPONS, don't move and don't participate in loop, so we have in effect target dummies -- can also use order 'dummy' or 'dummies' to switch to train if theSpawner.orders:lower() == "dummy" or theSpawner.orders:lower() == "dummies" then theSpawner.orders = "train" end if theSpawner.orders:lower() == "training" then theSpawner.orders = "train" end - theSpawner.range = cfxZones.getNumberFromZoneProperty(inZone, "range", 300) -- if we have a range, for example enemy detection for Lasing or engage range - theSpawner.maxSpawns = cfxZones.getNumberFromZoneProperty(inZone, "maxSpawns", -1) -- if there is a limit on how many troops can spawn. -1 = endless spawns - theSpawner.requestable = cfxZones.getBoolFromZoneProperty(inZone, "requestable", false) + theSpawner.range = inZone:getNumberFromZoneProperty("range", 300) -- if we have a range, for example enemy detection for Lasing or engage range + theSpawner.maxSpawns = inZone:getNumberFromZoneProperty("maxSpawns", -1) -- if there is a limit on how many troops can spawn. -1 = endless spawns + theSpawner.requestable = inZone:getBoolFromZoneProperty( "requestable", false) if theSpawner.requestable then theSpawner.paused = true if inZone.verbose or cfxSpawnZones.verbose then trigger.action.outText("+++spwn: spawner <" .. inZone.name .. "> paused: requestable enabled", 30) end end - if cfxZones.hasProperty(inZone, "target") then - theSpawner.target = cfxZones.getStringFromZoneProperty(inZone, "target", "") + if inZone:hasProperty("target") then + theSpawner.target = inZone:getStringFromZoneProperty("target", "") if theSpawner.target == "" then -- this is the defaut case theSpawner.target = nil end diff --git a/modules/cloneZone.lua b/modules/cloneZone.lua index e322b22..359590e 100644 --- a/modules/cloneZone.lua +++ b/modules/cloneZone.lua @@ -1,5 +1,5 @@ cloneZones = {} -cloneZones.version = "1.8.2" +cloneZones.version = "1.9.0" cloneZones.verbose = false cloneZones.requiredLibs = { "dcsCommon", -- always @@ -100,8 +100,12 @@ cloneZones.respawnOnGroupID = true - upgraded config zone parsing 1.8.1 - clone zone definition now supports quads 1.8.2 - on pre-wipe, delay respawn by 0.5s to avoid 'dropping' statics - - + 1.9.0 - minor clean-up for synonyms + - spawnWithSpawner alias for HeloTroops etc requestable SPAWN + - requestable attribute + - cooldown attribute + - cloner collects all types used + - groupScheme attribute --]]-- -- @@ -194,7 +198,7 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" if cloneZones.verbose or theZone.verbose then trigger.action.outText("+++clnZ: new cloner <" .. theZone.name ..">", 30) end - + theZone.spawnWithSpawner = cloneZones.spawnWithSpawner theZone.myUniqueCounter = cloneZones.lclUniqueCounter -- init local counter local localZones = cloneZones.allGroupsInZoneByData(theZone) @@ -226,11 +230,11 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" theZone.source = theZone:getStringFromZoneProperty("source", "") if theZone.source == "" then theZone.source = nil end end + theZone.allTypes = {} -- names of all types if not theZone.source then theZone.cloneNames = {} -- names of the groups. only present in template spawners - theZone.staticNames = {} -- names of all statics. only present in templates - + theZone.staticNames = {} -- names of all statics. only present in templates for idx, aGroup in pairs(localZones) do local gName = aGroup:getName() if gName then @@ -239,10 +243,19 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" -- now get group data and save a lookup for -- resolving internal references local rawData, cat, ctry = cfxMX.getGroupFromDCSbyName(gName) + -- iterate all units and save their individual types + for idy, aUnit in pairs(rawData.units) do + local theType = aUnit.type +-- trigger.action.outText("proccing type <" .. theType .. ">", 30) + if not theZone.allTypes[theType] then + theZone.allTypes[theType] = 1 -- first one + else + theZone.allTypes[theType] = theZone.allTypes[theType] + 1 -- increment + end + end local origID = rawData.groupId end end - for idx, aStatic in pairs (localObjects) do local sName = aStatic:getName() if sName then @@ -277,17 +290,11 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" -- f? and spawn? and other synonyms map to the same if theZone:hasProperty("f?") then theZone.spawnFlag = theZone:getStringFromZoneProperty("f?", "none") - end - - if theZone:hasProperty("in?") then + elseif theZone:hasProperty("in?") then theZone.spawnFlag = theZone:getStringFromZoneProperty("in?", "none") - end - - if theZone:hasProperty("spawn?") then + elseif theZone:hasProperty("spawn?") then theZone.spawnFlag = theZone:getStringFromZoneProperty("spawn?", "none") - end - - if theZone:hasProperty("clone?") then + elseif theZone:hasProperty("clone?") then theZone.spawnFlag = theZone:getStringFromZoneProperty("clone?", "none") end @@ -298,13 +305,9 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" -- deSpawn? if theZone:hasProperty("deSpawn?") then theZone.deSpawnFlag = theZone:getStringFromZoneProperty( "deSpawn?", "none") - end - - if theZone:hasProperty("deClone?") then + elseif theZone:hasProperty("deClone?") then theZone.deSpawnFlag = theZone:getStringFromZoneProperty( "deClone?", "none") - end - - if theZone:hasProperty("wipe?") then + elseif theZone:hasProperty("wipe?") then theZone.deSpawnFlag = theZone:getStringFromZoneProperty("wipe?", "none") end @@ -312,6 +315,9 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" theZone.lastDeSpawnValue = theZone:getFlagValue(theZone.deSpawnFlag) end + theZone.cooldown = theZone:getNumberFromZoneProperty("cooldown", -1) -- anything > 0 activates cd + theZone.lastSpawnTimeStamp = -10000 + theZone.onStart = theZone:getBoolFromZoneProperty("onStart", false) theZone.moveRoute = theZone:getBoolFromZoneProperty("moveRoute", false) @@ -354,6 +360,15 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" end end + -- interface to requestable, must be unsourced! + if theZone:hasProperty("requestable") then + theZone.requestable = theZone:getBoolFromZoneProperty( "requestable", false) + theZone.baseName = theZone.name -- backward compatibility with HeloTroops + if theZone.source then + trigger.action.outText("WARNING: cloner <" .. theZone.name .. "> has 'source' attribute and is marked 'requestable' - this can result in unrequestable clones", 30) + end + end + -- randomized locations on spawn theZone.rndLoc = theZone:getBoolFromZoneProperty("randomizedLoc", false) if theZone:hasProperty("rndLoc") then @@ -380,9 +395,14 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" theZone.nameScheme = theZone:getStringFromZoneProperty( "nameScheme", "-") -- default to [ "-" ] end + if theZone:hasProperty("groupScheme") then + theZone.groupScheme = theZone:getStringFromZoneProperty("groupScheme", "-") + end + if theZone.identical and theZone.nameScheme then - trigger.action.outText("+++clnZ: WARNING - clone zone <" .. theZone.name .. "> has both IDENTICAL and NAMESCHEME attributes. nameScheme is ignored.", 30) - theZone.nameScheme = nil + trigger.action.outText("+++clnZ: WARNING - clone zone <" .. theZone.name .. "> has both IDENTICAL and NAMESCHEME/GROUPSCHEME attributes. nameScheme is ignored.", 30) + theZone.nameScheme = nil + theZone.groupScheme = nil end -- we end with clear plate end @@ -629,14 +649,19 @@ end function cloneZones.uniqueNameGroupData(theData, theCloneZone, sourceName) if not sourceName then sourceName = theCloneZone.name end - theData.name = dcsCommon.uuid(theData.name) + if not theCloneZone.groupScheme then + theData.name = dcsCommon.uuid(theData.name) + else + theData.name = cloneZones.nameFromSchema(theCloneZone.groupScheme, theData.name, theCloneZone, sourceName, 1) + end + + local schema = theCloneZone.nameScheme local units = theData.units local iterCount = 1 local newName = "none" local allNames = {} -- enforce unique names inside group for idx, aUnit in pairs(units) do if theCloneZone and theCloneZone.nameScheme then - local schema = theCloneZone.nameScheme newName, iterCount = cloneZones.nameFromSchema(schema, aUnit.name, theCloneZone, sourceName, iterCount) -- make sure that this name is has not been generated yet @@ -1490,6 +1515,16 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) return spawnedGroups, spawnedStatics end +-- retro-fit for helo troops and others to provide 'requestable' support +function cloneZones.spawnWithSpawner(theZone) + -- analog to cfxSpawnZones.spawnWithSpawner(theSpawner) + -- glue code for helo troops and other modules + + -- we may want to check if cloner isn't emtpy first + + cloneZones.spawnWithCloner(theZone) +end + function cloneZones.spawnWithCloner(theZone) if not theZone then trigger.action.outText("+++clnZ: nil zone on spawnWithCloner", 30) @@ -1500,11 +1535,24 @@ function cloneZones.spawnWithCloner(theZone) return end + -- see if we are on cooldown. If so, exit + if theZone.cooldown > 0 then + local now = timer.getTime() + if now < theZone.lastSpawnTimeStamp + theZone.cooldown then + if theZone.verbose or cloneZones.verbose then + trigger.action.outText("+++clnZ: cloner <" .. theZone.name .. "> still on cool-down, no clone cycle", 30) + end + return + else + theZone.lastSpawnTimeStamp = now + end + 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 + -- source can be a comma separated list local templateName = theZone.source if dcsCommon.containsString(templateName, ",") then local allNames = templateName @@ -1615,18 +1663,8 @@ function cloneZones.hasLiveUnits(theZone) if theZone.mySpawns then for idx, aGroup in pairs(theZone.mySpawns) do if aGroup:isExist() then - -- an easier/faster method would be to invoke - -- aGroup:getSize() local uNum = aGroup:getSize() if uNum > 0 then return true end - --[[ - 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 end @@ -1642,6 +1680,49 @@ function cloneZones.hasLiveUnits(theZone) return false end +function cloneZones.resolveOwningCoalition(theZone) + if not theZone.masterOwner then return theZone.owner end + local masterZone = cfxZones.getZoneByName(theZone.masterOwner) + if not masterZone then + trigger.action.outText("+++clnZ: cloner " .. theZone.name .. " could not find master owner <" .. theZone.masterOwner .. ">", 30) + return theZone.owner + end + return masterZone.owner +end + +function cloneZones.getRequestableClonersInRange(aPoint, aRange, aSide) + if not aSide then aSide = 0 end + if not aRange then aRange = 200 end + if not aPoint then return {} end + + local theSpawners = {} + for idx, aZone in pairs(cloneZones.cloners) do + -- iterate all zones and collect those that match + local hasMatch = true + local delta = dcsCommon.distFlat(aPoint, aZone:getPoint()) + if delta > aRange then hasMatch = false end + if aSide ~= 0 then + -- check if side is correct for owned zone + local resolved = cloneZones.resolveOwningCoalition(aZone) + --if resolved ~= 0 and resolved ~= aSide then + if resolved == 0 or resolved ~= aSide then + -- failed ownership test. must match and not be zero + hasMatch = false + end + end + + if not aZone.requestable then + hasMatch = false + end + + if hasMatch then + table.insert(theSpawners, aZone) + end + end + + return theSpawners +end + -- -- UPDATE -- @@ -1672,14 +1753,7 @@ function cloneZones.update() -- 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) - cfxZones.pollFlag(aZone.emptyFlag, 'inc', aZone) - end - --]]-- - + -- see if we need to bang a flag if aZone.emptyBangFlag then aZone:pollFlag(aZone.emptyBangFlag, aZone.cloneMethod) if cloneZones.verbose then @@ -2049,8 +2123,6 @@ end - FAC Assign group - set freq for unit - -- fixedName: immutable name, no safe renaming. always use ID and name without changing it. very special use case. nameTest - optional safety / debug feature that will name-test each unit that is about to be spawned for replacement. Maybe auto turn on when verbose is set? - identical - make a clone of template and do not touch name nor id. will fully replace make example where transport can be different plane types but have same name --]]-- \ No newline at end of file diff --git a/modules/raiseFlag.lua b/modules/raiseFlag.lua index ee8fbf6..63a0fbb 100644 --- a/modules/raiseFlag.lua +++ b/modules/raiseFlag.lua @@ -15,6 +15,10 @@ raiseFlag.flags = {} 1.1.0 - DML update 1.2.0 - Watchflag update 1.2.1 - support for 'inc', 'dec', 'flip' + 2.0.0 - dmlZones + - full method support + - full DML upgrade + - method attribute (synonym to 'value' --]]-- function raiseFlag.addRaiseFlag(theZone) @@ -43,22 +47,28 @@ function raiseFlag.createRaiseFlagWithZone(theZone) theZone.raiseFlag = cfxZones.getStringFromZoneProperty(theZone, "raiseFlag!", "") -- the flag to raise end - theZone.flagValue = cfxZones.getStringFromZoneProperty(theZone, "value", "inc") -- value to set to. default is command 'inc' + -- pre-method DML raiseFlag is now upgraded to method. + -- flagValue now carries the method + if theZone:hasProperty("value") then -- backward compatibility + theZone.flagValue = theZone:getStringFromZoneProperty("value", "inc") -- value to set to. default is command 'inc' + else + theZone.flagValue = theZone:getStringFromZoneProperty("method", "inc") + end theZone.flagValue = theZone.flagValue:lower() - theZone.minAfterTime, theZone.maxAfterTime = cfxZones.getPositiveRangeFromZoneProperty(theZone, "afterTime", -1) + theZone.minAfterTime, theZone.maxAfterTime = theZone:getPositiveRangeFromZoneProperty("afterTime", -1) -- method for triggering -- watchflag: -- triggerMethod - theZone.raiseTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change") - if cfxZones.hasProperty(theZone, "raiseTriggerMethod") then - theZone.raiseTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "raiseTriggerMethod", "change") + theZone.raiseTriggerMethod = theZone:getStringFromZoneProperty( "triggerMethod", "change") + if theZone:hasProperty("raiseTriggerMethod") then + theZone.raiseTriggerMethod = theZone:getStringFromZoneProperty("raiseTriggerMethod", "change") end - if cfxZones.hasProperty(theZone, "stopFlag?") then - theZone.triggerStopFlag = cfxZones.getStringFromZoneProperty(theZone, "stopFlag?", "none") - theZone.lastTriggerStopValue = cfxZones.getFlagValue(theZone.triggerStopFlag, theZone) -- save last value + if theZone:hasProperty("stopFlag?") then + theZone.triggerStopFlag = theZone:getStringFromZoneProperty( "stopFlag?", "none") + theZone.lastTriggerStopValue = theZone:getFlagValue(theZone.triggerStopFlag) -- save last value end theZone.scheduleID = nil @@ -80,6 +90,12 @@ function raiseFlag.triggered(args) if theZone.raiseStopped then return end -- if we get here, we aren't stopped and do the flag pull local command = theZone.flagValue + theZone:pollFlag(theZone.raiseFlag, command) + if raiseFlag.verbose or theZone.verbose then + trigger.action.outText("+++rFlg - raising <" .. theZone.raiseFlag .. "> with method '" .. command .. "'" ,30) + end + + --[[-- command = dcsCommon.trim(command) if command == "inc" or command == "dec" or command == "flip" then cfxZones.pollFlag(theZone.raiseFlag, command, theZone) @@ -92,6 +108,7 @@ function raiseFlag.triggered(args) trigger.action.outText("+++rFlg - raising <" .. theZone.raiseFlag .. "> to value: " .. theZone.flagValue ,30) end end + --]]-- end -- @@ -103,20 +120,10 @@ function raiseFlag.update() for idx, aZone in pairs(raiseFlag.flags) do -- make sure to re-start before reading time limit - if cfxZones.testZoneFlag(aZone, aZone.triggerStopFlag, aZone.raiseTriggerMethod, "lastTriggerStopValue") then + if aZone:testZoneFlag(aZone.triggerStopFlag, aZone.raiseTriggerMethod, "lastTriggerStopValue") then theZone.raiseStopped = true -- we are done, no flag! end - -- old code - --[[-- - 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 @@ -127,13 +134,10 @@ end 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 + theZone = cfxZones.createSimpleZone("raiseFlagConfig") end - raiseFlag.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + raiseFlag.verbose = theZone.verbose if raiseFlag.verbose then trigger.action.outText("+++rFlg: read config", 30) @@ -178,6 +182,3 @@ if not raiseFlag.start() then trigger.action.outText("cfx Raise Flag aborted: missing libraries", 30) raiseFlag = nil end - --- add rnd(a,b) support to value --- better: if value is a range, make it a random. problem: negative values are legal, so we need formula \ No newline at end of file diff --git a/modules/stopGaps.lua b/modules/stopGaps.lua index b7aba7e..6946dbf 100644 --- a/modules/stopGaps.lua +++ b/modules/stopGaps.lua @@ -1,5 +1,5 @@ stopGap = {} -stopGap.version = "1.0.8" +stopGap.version = "1.0.9" stopGap.verbose = false stopGap.ssbEnabled = true stopGap.ignoreMe = "-sg" diff --git a/tutorial & demo missions/demo - Send in the Clones.miz b/tutorial & demo missions/demo - Send in the Clones.miz new file mode 100644 index 0000000..d91907d Binary files /dev/null and b/tutorial & demo missions/demo - Send in the Clones.miz differ diff --git a/tutorial & demo missions/demo - helo trooper.miz b/tutorial & demo missions/demo - helo trooper.miz index b42bb12..591d7cf 100644 Binary files a/tutorial & demo missions/demo - helo trooper.miz and b/tutorial & demo missions/demo - helo trooper.miz differ