diff --git a/Doc/DML Documentation.pdf b/Doc/DML Documentation.pdf index 3790ccd..feb9ec6 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 bfd85ac..aacb8a1 100644 Binary files a/Doc/DML Quick Reference.pdf and b/Doc/DML Quick Reference.pdf differ diff --git a/modules/cfxSSBClient.lua b/modules/cfxSSBClient.lua index 8f62fcc..fc13f1d 100644 --- a/modules/cfxSSBClient.lua +++ b/modules/cfxSSBClient.lua @@ -36,7 +36,8 @@ Version History - reUseAfter option for single-use - dcsCommon, cfxZones import 2.0.1 - stricter verbosity: moved more comments to verbose only - 2.0.2 - health check code + 2.0.2 - health check code (initial) + - added verbosity WHAT IT IS SSB Client is a small script that forms the client-side counterpart to @@ -235,15 +236,21 @@ end function cfxSSBClient:onEvent(event) if event.id == 21 then -- S_EVENT_PLAYER_LEAVE_UNIT - trigger.action.outText("+++SSB: Player leave unit", 30) + if cfxSSBClient.verbose then + trigger.action.outText("+++SSB: Player leave unit", 30) + end local theUnit = event.initiator - if not theUnit then - trigger.action.outText("+++SSB: No unit left, abort", 30) + if not theUnit then + if cfxSSBClient.verbose then + trigger.action.outText("+++SSB: No unit left, abort", 30) + end return end local curH = theUnit:getLife() local maxH = theUnit:getLife0() - trigger.action.outText("+++SSB: Health check: " .. curH .. " of " .. maxH, 30) + if cfxSSBClient.verbose then + trigger.action.outText("+++SSB: Health check: " .. curH .. " of " .. maxH, 30) + end return end diff --git a/modules/civAir.lua b/modules/civAir.lua index 9d2119a..6bd66f8 100644 --- a/modules/civAir.lua +++ b/modules/civAir.lua @@ -20,7 +20,7 @@ civAir.version = "1.4.0" --]]-- -civAir.ups = 0.05 -- updates per second +civAir.ups = 0.05 -- updates per second. 0.05 = once every 20 seconds civAir.initialAirSpawns = true -- when true has population spawn in-air at start civAir.verbose = false diff --git a/modules/cloneZone.lua b/modules/cloneZone.lua index db7c1dc..dcc48d6 100644 --- a/modules/cloneZone.lua +++ b/modules/cloneZone.lua @@ -1,11 +1,13 @@ cloneZones = {} -cloneZones.version = "1.3.0" +cloneZones.version = "1.3.1" cloneZones.verbose = false cloneZones.requiredLibs = { "dcsCommon", -- always "cfxZones", -- Zones, of course "cfxMX", } +-- groupTracker is OPTIONAL! and required only with trackWith attribute + cloneZones.cloners = {} cloneZones.callbacks = {} cloneZones.unitXlate = {} @@ -32,6 +34,8 @@ cloneZones.uniqueCounter = 9200000 -- we start group numbering here - clone? synonym - empty! and method attributes 1.3.0 - DML flag upgrade + 1.3.1 - groupTracker interface + - trackWith: attribute --]]-- @@ -238,6 +242,11 @@ function cloneZones.createClonerWithZone(theZone) -- has "Cloner" theZone.turn = cfxZones.getNumberFromZoneProperty(theZone, "turn", 0) + -- interface to groupTracker + if cfxZones.hasProperty(theZone, "trackWith:") then + theZone.trackWith = cfxZones.getStringFromZoneProperty(theZone, "trackWith:", "") + end + -- we end with clear plate end @@ -523,6 +532,23 @@ function cloneZones.resolveReferences(theZone, dataTable) end end + +function cloneZones.handoffTracking(theGroup, theZone) + if not groupTracker then + trigger.action.outText("+++clne: <" .. theZone.name .. "> trackWith requires groupTracker module", 30) + return + end + local trackerName = theZone.trackWith + if trackerName == "*" then trackerName = theZone.name end + local theTracker = groupTracker.getTrackerByName(trackerName) + if not theTracker then + trigger.action.outText("+++clne: <" .. theZone.name .. ">: cannot find tracker named <".. trackerName .. ">", 30) + return + end + + groupTracker.addGroupToTracker(theGroup, theTracker) +end + function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) -- theZone is the cloner with the template -- spawnZone is the spawner with settings @@ -612,6 +638,10 @@ function cloneZones.spawnWithTemplateForZone(theZone, spawnZone) end cloneZones.invokeCallbacks(theZone, "did spawn group", theGroup) + -- interface to groupTracker + if theZone.trackWith then + cloneZones.handoffTracking(theGroup, theZone) + end end -- static spawns diff --git a/modules/csarManager2.lua b/modules/csarManager2.lua index 7bc8aa5..668ee78 100644 --- a/modules/csarManager2.lua +++ b/modules/csarManager2.lua @@ -1,5 +1,8 @@ csarManager = {} -csarManager.version = "2.0.3" +csarManager.version = "2.1.0" +csarManager.verbose = false +csarManager.ups = 1 + --[[-- VERSION HISTORY - 1.0.0 initial version - 1.0.1 - smoke optional @@ -20,6 +23,18 @@ csarManager.version = "2.0.3" - use hoverDuration - 2.0.3 - corrected bug in hoverDuration - 2.0.4 - guard in createCSARMission for cfxCommander + - 2.1.0 - startCSAR? + - deferrable missions + - verbose + - ups + - useSmoke + - smokeColor + - reworked smoking the loc + - config zone + - csarRedDelivered + - csarBlueDelivered + - finally fixed smoke performance bug + - csarManager.vectoring optional --]]-- -- modules that need to be loaded BEFORE I run @@ -33,6 +48,7 @@ csarManager.requiredLibs = { } -- *** DOES NOT EXTEND ZONES *** BUT USES OWN STRUCT +-- *** extends zones for csarMission zones though --[[-- CSAR MANAGER @@ -103,6 +119,7 @@ csarManager.requiredLibs = { -- OPTIONS -- csarManager.useSmoke = false -- smoke is a performance killer, so you can turn it off +csarManager.smokeColor = 4 -- when using smoke -- unitConfigs contain the config data for any helicopter @@ -115,6 +132,7 @@ csarManager.myEvents = {3, 4, 5} -- 3 = take off, 4 = land, 5 = crash -- csarManager.openMissions = {} -- all currently available missions csarManager.csarBases = {} -- all bases where we can drop off rescued pilots +csarManager.csarZones = {} -- zones for spawning csarManager.missionID = 1 -- to create uuid csarManager.rescueRadius = 70 -- must land within 50m to rescue @@ -124,6 +142,7 @@ csarManager.hoverDuration = 20 -- must hover for this duration csarManager.rescueTriggerRange = 2000 -- when the unit pops smoke and radios csarManager.beaconSound = "Radio_beacon_of_distress_on_121,5_MHz.ogg" csarManager.pilotWeight = 120 -- kg for the rescued person. added to the unit's weight +csarManager.vectoring = true -- provide bearing and range -- -- callbacks -- @@ -176,7 +195,7 @@ function csarManager.createDownedPilot(theMission) cfxCommander.scheduleCommands(theCommands, 2) -- in 2 seconds, so unit has time to percolate through DCS end -function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, timeLimit, mapMarker) +function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, timeLimit, mapMarker, inRadius) -- create a type if not timeLimit then timeLimit = -1 end if not point then return nil end @@ -186,9 +205,10 @@ function csarManager.createCSARMissionData(point, theSide, freq, name, numCrew, -- remove "downed" - it will be added again later name = dcsCommon.removePrefix(name, "(downed) ") end + if not inRadius then inRadius = csarManager.rescueRadius end newMission.name = "(downed) " .. name .. "-" .. csarManager.missionID -- make it uuid-capable - newMission.zone = cfxZones.createSimpleZone(newMission.name, point, csarManager.rescueRadius) + newMission.zone = cfxZones.createSimpleZone(newMission.name, point, inRadius) --csarManager.rescueRadius) newMission.marker = mapMarker -- so it can be removed later newMission.isHot = false -- creating adversaries will make it hot, or when units are near. maybe implement a search later? -- detection and load stuff @@ -354,10 +374,24 @@ function csarManager.successMission(who, where, theMission) -- now call callback for coalition side -- callback has format callback(coalition, success true/false, numberSaved, descriptionText) - for idx, callback in pairs(csarManager.csarCompleteCB) do - callback(theMission.side, true, 1, "test") - end + csarManager.invokeCallbacks(theMission.side, true, 1, "success") +-- for idx, callback in pairs(csarManager.csarCompleteCB) do +-- callback(theMission.side, true, 1, "test") +-- end trigger.action.outSoundForCoalition(theMission.side, "Quest Snare 3.wav") + + if csarManager.csarRedDelivered and theMission.side == 1 then + cfxZones.pollFlag(csarManager.csarRedDelivered, "inc", csarManager.configZone) + end + + if csarManager.csarBlueDelivered and theMission.side == 2 then + cfxZones.pollFlag(csarManager.csarBlueDelivered, "inc", csarManager.configZone) + end + + if csarManager.csarDelivered then + cfxZones.pollFlag(csarManager.csarDelivered, "inc", csarManager.configZone) + trigger.action.outText("+++csar: banging csarDelivered: <" .. csarManager.csarDelivered .. ">", 30) + end end function csarManager.heloLanded(theUnit) @@ -687,7 +721,12 @@ function csarManager.doListCSARRequests(args) d = math.floor(d * 10) / 10 local b = dcsCommon.bearingInDegreesFromAtoB(point, mission.zone.point) local status = "alive" - report = report .. "\n".. mission.name .. ", bearing " .. b .. ", " ..d .."nm, " .. " ADF " .. mission.freq .. "0 kHz - " .. status + if csarManager.vectoring then + report = report .. "\n".. mission.name .. ", bearing " .. b .. ", " ..d .."nm, " .. " ADF " .. mission.freq .. "0 kHz - " .. status + else + -- leave out vectoring + report = report .. "\n".. mission.name .. " ADF " .. mission.freq .. "0 kHz - " .. status + end end end @@ -879,7 +918,7 @@ end function csarManager.update() -- every second -- schedule next invocation - timer.scheduleFunction(csarManager.update, {}, timer.getTime() + 1) + timer.scheduleFunction(csarManager.update, {}, timer.getTime() + 1/csarManager.ups) -- first, check the health of all csar misions and update the table of live units csarManager.updateCSARMissions() @@ -924,8 +963,11 @@ function csarManager.update() -- every second end -- also pop smoke if not popped already, or more than 3 minutes ago if csarManager.useSmoke and timer.getTime() - csarMission.lastSmokeTime > 179 then - local smokePoint = cfxZones.createHeightCorrectedPoint(csarMission.zone.point) - trigger.action.smoke(smokePoint, 4 ) + local smokePoint = dcsCommon.randomPointOnPerimeter( + 50, csarMission.zone.point.x, csarMission.zone.point.z) --cfxZones.createHeightCorrectedPoint(csarMission.zone.point) + -- trigger.action.smoke(smokePoint, 4 ) + dcsCommon.markPointWithSmoke(smokePoint, csarManager.smokeColor) + csarMission.lastSmokeTime = timer.getTime() end -- now check if we are inside hover range and alt @@ -990,6 +1032,32 @@ function csarManager.update() -- every second end -- if troop carrier end -- if exists end -- for all players + + -- now see and check if we need to spawn from a csar zone + -- that has been told to spawn + for idx, theZone in pairs(csarManager.csarZones) do + -- check if their flag value has changed + if theZone.startCSAR then + -- this should always be true, but you never know + local currVal = cfxZones.getFlagValue(theZone.startCSAR, theZone) + if currVal ~= theZone.lastCSARVal then + local theMission = csarManager.createCSARMissionData( + cfxZones.getPoint(theZone), + theZone.csarSide, + theZone.csarFreq, + theZone.csarName, + theZone.numCrew, + theZone.timeLimit, + theZone.csarMapMarker, + theZone.radius) + csarManager.addMission(theMission) + theZone.lastCSARVal = currVal + if csarManager.verbose then + trigger.action.outText("+++csar: started CSAR mission " .. theZone.csarName, 30) + end + end + end + end end -- @@ -1036,10 +1104,9 @@ function csarManager.createCSARforUnit(theUnit, pilotName, radius, silent) end - --- --- Init & Start -- +-- csar (mission) zones +-- function csarManager.processCSARBASE() local csarBases = cfxZones.zonesWithProperty("CSARBASE") @@ -1051,15 +1118,85 @@ function csarManager.processCSARBASE() end end -function csarManager.processCASRZones() +function csarManager.addCSARZone(theZone) + table.insert(csarManager.csarZones, theZone) +end + +function csarManager.readCSARZone(theZone) + -- zones have attribute "CSAR" + -- gather data, and then create a mission from this + local theSide = cfxZones.getCoalitionFromZoneProperty(theZone, "coalition", 0) + theZone.csarSide = theSide + theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "name", "") + if cfxZones.hasProperty(theZone, "csarName") then + theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "csarName", "") + end + if cfxZones.hasProperty(theZone, "pilotName") then + theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "pilotName", "") + end + + if cfxZones.hasProperty(theZone, "victimName") then + theZone.csarName = cfxZones.getStringFromZoneProperty(theZone, "victimName", "") + end + + theZone.csarFreq = cfxZones.getNumberFromZoneProperty(theZone, "freq", 0) + if theZone.csarFreq == 0 then theZone.csarFreq = nil end + theZone.numCrew = 1 + theZone.csarMapMarker = nil + theZone.timeLimit = cfxZones.getNumberFromZoneProperty(theZone, "timeLimit", 0) + if theZone.timeLimit == 0 then theZone.timeLimit = nil else theZone.timeLimit = timeLimit * 60 end + + local deferred = cfxZones.getBoolFromZoneProperty(theZone, "deferred", false) + + if cfxZones.hasProperty(theZone, "in?") then + theZone.startCSAR = cfxZones.getStringFromZoneProperty(theZone, "in?", "*none") + theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone) + end + + if cfxZones.hasProperty(theZone, "startCSAR?") then + theZone.startCSAR = cfxZones.getStringFromZoneProperty(theZone, "startCSAR?", "*none") + theZone.lastCSARVal = cfxZones.getFlagValue(theZone.startCSAR, theZone) + end + + + if (not deferred) then + local theMission = csarManager.createCSARMissionData( + theZone.point, + theZone.csarSide, + theZone.csarFreq, + theZone.csarName, + theZone.numCrew, + theZone.timeLimit, + theZone.csarMapMarker, + theZone.radius) + csarManager.addMission(theMission) + end + + -- add to list of startable csar + if theZone.startCSAR then + csarManager.addCSARZone(theZone) + trigger.action.outText("csar: added <".. theZone.name .."> to deferred csar missions", 30) + end + + if deferred and not theZone.startCSAR then + trigger.action.outText("+++csar: warning - CSAR Mission in Zone <" .. theZone.name .. "> can't be started", 30) + end +end + +function csarManager.processCSARZones() local csarBases = cfxZones.zonesWithProperty("CSAR") -- now add all zones to my zones table, and init additional info -- from properties for k, aZone in pairs(csarBases) do + + csarManager.readCSARZone(aZone) + --[[-- -- gather data, and then create a mission from this local theSide = cfxZones.getCoalitionFromZoneProperty(aZone, "coalition", 0) + aZone.csarSide = theSide local name = cfxZones.getZoneProperty(aZone, "name") + aZone. local freq = cfxZones.getNumberFromZoneProperty(aZone, "freq", 0) if freq == 0 then freq = nil end local numCrew = 1 @@ -1075,9 +1212,17 @@ function csarManager.processCASRZones() timeLimit, mapMarker) csarManager.addMission(theMission) + --]]-- + end end +-- +-- Init & Start +-- + + + function csarManager.invokeCallbacks(theCoalition, success, numRescued, notes) -- invoke anyone who wants to know that a group @@ -1091,6 +1236,53 @@ function csarManager.installCallback(theCB) table.insert(csarManager.csarCompleteCB, theCB) end +function csarManager.readConfigZone() + local theZone = cfxZones.getZoneByName("csarManagerConfig") + if not theZone then + if csarManager.verbose then + trigger.action.outText("+++csar: NO config zone!", 30) + end + return + end + csarManager.configZone = theZone -- save for flag banging compatibility + + csarManager.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + + csarManager.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1) + + csarManager.useSmoke = cfxZones.getBoolFromZoneProperty(theZone, "useSmoke", true) + csarManager.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "blue") + csarManager.smokeColor = dcsCommon.smokeColor2Num(csarManager.smokeColor) + + + if cfxZones.hasProperty(theZone, "csarRedDelivered!") then + csarManager.csarRedDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarRedDelivered!", "*") + end + + if cfxZones.hasProperty(theZone, "csarBlueDelivered!") then + csarManager.csarBlueDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarBlueDelivered!", "*") + end + + if cfxZones.hasProperty(theZone, "csarDelivered!") then + csarManager.csarDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarDelivered!", "*") + trigger.action.outText("+++csar: will bang csarDelivered: <" .. csarManager.csarDelivered .. ">", 30) + end + + csarManager.rescueRadius = cfxZones.getNumberFromZoneProperty(theZone, "rescueRadius", 70) --70 -- must land within 50m to rescue + csarManager.hoverRadius = cfxZones.getNumberFromZoneProperty(theZone, "hoverRadius", 30) -- 30 -- must hover within 10m of unit + csarManager.hoverAlt = cfxZones.getNumberFromZoneProperty(theZone, "hoverAlt", 40) -- 40 -- must hover below this alt + csarManager.hoverDuration = cfxZones.getNumberFromZoneProperty(theZone, "hoverDuration", 20) -- 20 -- must hover for this duration + csarManager.rescueTriggerRange = cfxZones.getNumberFromZoneProperty(theZone, "rescueTriggerRange", 2000) -- 2000 -- when the unit pops smoke and radios + csarManager.beaconSound = cfxZones.getStringFromZoneProperty(theZone, "beaconSound", "Radio_beacon_of_distress_on_121.ogg") --"Radio_beacon_of_distress_on_121,5_MHz.ogg" + csarManager.pilotWeight = cfxZones.getNumberFromZoneProperty(theZone, "pilotWeight", 120) -- 120 + + csarManager.vectoring = cfxZones.getBoolFromZoneProperty(theZone, "vectoring", true) + + if csarManager.verbose then + trigger.action.outText("+++csar: read config", 30) + end +end + function csarManager.start() -- make sure we have loaded all relevant libraries @@ -1099,6 +1291,9 @@ function csarManager.start() return false end + -- read config + csarManager.readConfigZone() + -- install callbacks for helo-relevant events dcsCommon.addEventHandler(csarManager.somethingHappened, csarManager.preProcessor, csarManager.postProcessor) @@ -1118,7 +1313,7 @@ function csarManager.start() -- now scan all zones to create ME-placed CSAR missions -- and populate the available mission. - csarManager.processCASRZones() + csarManager.processCSARZones() -- now call update so we can monitor progress of all helos, and alert them -- when they are close to a CSAR diff --git a/modules/groupTrackers.lua b/modules/groupTrackers.lua new file mode 100644 index 0000000..0e976d9 --- /dev/null +++ b/modules/groupTrackers.lua @@ -0,0 +1,242 @@ +groupTracker = {} +groupTracker.version = "1.0.0" +groupTracker.verbose = false +groupTracker.ups = 1 +groupTracker.requiredLibs = { + "dcsCommon", -- always + "cfxZones", -- Zones, of course +} +groupTracker.trackers = {} + +--[[-- + Version History + 1.0.0 - Initial version + +--]]-- + +function groupTracker.addTracker(theZone) + table.insert(groupTracker.trackers, theZone) +end + +function groupTracker.getTrackerByName(aName) + for idx, aZone in pairs(groupTracker.trackers) do + if aName == aZone.name then return aZone end + end + if groupTracker.verbose then + trigger.action.outText("+++msgr: no tracker with name <" .. aName ..">", 30) + end + + return nil +end + +-- +-- read attributes +-- + +-- +-- adding a group to a tracker - called by other modules and API +-- +function groupTracker.addGroupToTracker(theGroup, theTracker) + if groupTracker.verbose then + trigger.action.outText("+++gTrk: will add group <" .. theGroup:getName() .. "> to tracker " .. theTracker.name, 30) + end + + -- we have the tracker, add the group + if not theTracker.trackedGroups then theTracker.trackedGroups = {} end + table.insert(theTracker.trackedGroups, theGroup) + + -- now bang/invoke addGroup! + if theTracker.tAddGroup then + cfxZones.pollFlag(theTracker.tAddGroup, "inc", theTracker) + end + + -- now set numGroups + if theTracker.tNumGroups then + cfxZones.setFlagValue(theTracker.tNumGroups, #theTracker.trackedGroups, theTracker) + end + + -- invoke callbacks +end + +function groupTracker.addGroupToTrackerNamed(theGroup, trackerName) + if not trackerName then + trigger.action.outText("+++gTrk: nil tracker in addGroupToTrackerNamed", 30) + end + if not theGroup then + trigger.action.outText("+++gTrk: no group in addGroupToTrackerNamed <" .. trackerName .. ">", 30) + return + end + + if not theGroup:isExist() then + trigger.action.outText("+++gTrk: group does not exist in when adding to tracker <" .. trackerName .. ">", 30) + return + end + + + groupTracker.addGroupToTracker(theGroup, theTracker) +end + +-- read zone +function groupTracker.createTrackerWithZone(theZone) + -- init group tracking set + theZone.trackedGroups = {} + + if cfxZones.hasProperty(theZone, "numGroups!") then + theZone.tNumGroups = cfxZones.getStringFromZoneProperty(theZone, "numGroups!", "") + -- we may need to zero this flag + end + + if cfxZones.hasProperty(theZone, "addGroup!") then + theZone.tAddGroup = cfxZones.getStringFromZoneProperty(theZone, "addGroup!", "") + -- we may need to zero this flag + end + + if cfxZones.hasProperty(theZone, "removeGroup!") then + theZone.tRemoveGroup = cfxZones.getStringFromZoneProperty(theZone, "removeGroup!", "") + -- we may need to zero this flag + end + +end + +-- +-- update +-- +function groupTracker.checkGroups(theZone) + local filteredGroups = {} + for idx, theGroup in pairs(theZone.trackedGroups) do + -- see if this group can be transferred + local isDead = false + if theGroup:isExist() then + local allUnits = theGroup:getUnits() + isDead = true + for idy, aUnit in pairs(allUnits) do + if aUnit:getLife() > 1 then + isDead = false -- at least one living unit + break + end + end + else + isDead = true -- no longer exists + end + + if isDead then + -- bang deceased + if groupTracker.verbose then + trigger.action.outText("+++gTrk: dead group detected in " .. theZone.name .. ", discarding.", 30) + end + if theZone.tRemoveGroup then + cfxZones.pollFlag(theZone.tRemoveGroup, "inc", theZone) + end + else + -- transfer alive group + table.insert(filteredGroups, theGroup) + end + + end + -- now exchange filtered for current + theZone.trackedGroups = filteredGroups + --set new group value + -- now set numGroups if defined + if theZone.tNumGroups then + cfxZones.setFlagValue(theZone.tNumGroups, #filteredGroups, theZone) + end +end + +function groupTracker.update() + -- call me in a second to poll triggers + timer.scheduleFunction(groupTracker.update, {}, timer.getTime() + 1/groupTracker.ups) + + for idx, theZone in pairs(groupTracker.trackers) do + groupTracker.checkGroups(theZone) + end +end + +-- +-- Config & Start +-- +function groupTracker.trackGroupsInZone(theZone) + + local trackerName = cfxZones.getStringFromZoneProperty(theZone, "addToTracker:", "") + if trackerName == "*" then trackerName = theZone.name end + + local theTracker = groupTracker.getTrackerByName(trackerName) + if not theTracker then + trigger.action.outText("+++gTrk: trackGroupsInZone - no zone named <" .. trackerName .. ">", 30 ) + return + end + + local theGroups = cfxZones.allGroupsInZone(theZone, nil) + for idx, aGroup in pairs(theGroups) do + if groupTracker.verbose then + trigger.action.outText("+++gTrk: <" .. theZone.name .. "> passed off group <" .. aGroup:getName() .. "> to <" .. trackerName .. ">", 30) + end + groupTracker.addGroupToTracker(aGroup, theTracker) + end + +end + + +function groupTracker.readConfigZone() + local theZone = cfxZones.getZoneByName("groupTrackerConfig") + if not theZone then + if groupTracker.verbose then + trigger.action.outText("+++gTrk: NO config zone!", 30) + end + return + end + + groupTracker.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) + + groupTracker.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1) + + if groupTracker.verbose then + trigger.action.outText("+++gTrk: read config", 30) + end +end + +function groupTracker.start() + -- lib check + if not dcsCommon.libCheck then + trigger.action.outText("cfx Group Tracker requires dcsCommon", 30) + return false + end + if not dcsCommon.libCheck("cfx Group Tracker", groupTracker.requiredLibs) then + return false + end + + -- read config + groupTracker.readConfigZone() + + -- process tracker Zones + local attrZones = cfxZones.getZonesWithAttributeNamed("tracker") + for k, aZone in pairs(attrZones) do + groupTracker.createTrackerWithZone(aZone) -- process attributes + groupTracker.addTracker(aZone) -- add to list + end + + -- find and process all zones that want me to immediately add + -- units to the tracker + local attrZones = cfxZones.getZonesWithAttributeNamed("addToTracker:") + for k, aZone in pairs(attrZones) do + groupTracker.trackGroupsInZone(aZone) -- process attributes + end + + -- start update + groupTracker.update() + + trigger.action.outText("cfx Group Tracker v" .. groupTracker.version .. " started.", 30) + return true +end + +-- let's go! +if not groupTracker.start() then + trigger.action.outText("cfx Group Tracker aborted: missing libraries", 30) + messenger = nil +end + +--[[-- + add a pass to immediately add units in zones with 'addToTracker' + after zone pass + + add an output flag for the total number of units it watches, so when that changes, a change is instigated automatically +--]]-- \ No newline at end of file diff --git a/modules/guardianAngel.lua b/modules/guardianAngel.lua index d8d2c3b..623fc81 100644 --- a/modules/guardianAngel.lua +++ b/modules/guardianAngel.lua @@ -1,5 +1,5 @@ guardianAngel = {} -guardianAngel.version = "2.0.2" +guardianAngel.version = "2.0.3" guardianAngel.ups = 10 guardianAngel.launchWarning = true -- detect launches and warn pilot guardianAngel.intervention = true -- remove missiles just before hitting @@ -35,6 +35,8 @@ guardianAngel.requiredLibs = { - private option 2.0.2 - poof! explosion option to show explosion on intervention - can be dangerous + 2.0.3 - fxDistance + - mea cupa capability This script detects missiles launched against protected aircraft an @@ -43,7 +45,8 @@ removes them when they are about to hit --]]-- guardianAngel.minMissileDist = 50 -- m. below this distance the missile is killed by god, not the angel :) -guardianAngel.myEvents = {1, 15, 20, 21, 23} -- 1 - shot, 15 - birth, 20 - enter unit, 21 - player leave unit, 23 - start shooting +guardianAngel.myEvents = {1, 15, 20, 21, 23, 2} -- 1 - shot, 15 - birth, 20 - enter unit, 21 - player leave unit, 23 - start shooting +-- added 2 (hit) event to see if angel was defeated guardianAngel.safetyFactor = 1.8 -- for calculating dealloc range guardianAngel.unitsToWatchOver = {} -- I'll watch over these @@ -123,13 +126,7 @@ function guardianAngel.createQItem(theWeapon, theTarget, detectProbability) theItem.missed = false -- just keep watching for re-ack return theItem end ---[[-- -function guardianAngel.detectItem(theItem) - if theItem.detected then return end - -- perform detection calculations here - -end ---]]-- + -- calculate a point in direction from plane (pln) to weapon (wpn), dist meters function guardianAngel.calcSafeExplosionPoint(wpn, pln, dist) @@ -137,9 +134,27 @@ function guardianAngel.calcSafeExplosionPoint(wpn, pln, dist) local v = dcsCommon.vNorm(dirToWpn) -- |v| = 1 local v = dcsCommon.vMultScalar(v, dist) -- |v| = dist local newPoint = dcsCommon.vAdd(pln, v) + --trigger.action.outText("+++ gA: safe dist is ".. dist, 30) return newPoint end +function guardianAngel.bubbleCheck(wPos, w) + if true then return false end + for idx, aProtectee in pairs (guardianAngel.unitsToWatchOver) do + local uP = aProtectee:getPoint() + local d = math.floor(dcsCommon.dist(wPos, uP)) + if d < guardianAngel.minMissileDist * 2 then + trigger.action.outText("+++gA: gazing at w=" .. w:getName() .. " APR:" .. aProtectee:getName() .. ", d=" .. d .. ", cutoff=" .. guardianAngel.minMissileDist, 30) + if w:getTarget() then + trigger.action.outText("+++gA: w is targeting " .. w:getTarget():getName(), 30) + else + trigger.action.outText("+++gA: w is NOT targeting anything") + end + end + end + return false +end + function guardianAngel.monitorItem(theItem) local w = theItem.theWeapon local ID = theItem.tID @@ -164,6 +179,10 @@ function guardianAngel.monitorItem(theItem) local oldWPos = theItem.wP local A = w:getPoint() -- A is new point of weapon theItem.wp = A -- update new position, old is in oldWPos + + -- new code: safety check with ALL protected wings + local bubbleThreat = guardianAngel.bubbleCheck(A, w) + local B if currentTarget then B = currentTarget:getPoint() else B = A end @@ -202,7 +221,7 @@ function guardianAngel.monitorItem(theItem) -- now add some showy explosion so the missile -- doesn't just disappear if guardianAngel.explosion > 0 then - local xP = guardianAngel.calcSafeExplosionPoint(A,B, 500) + local xP = guardianAngel.calcSafeExplosionPoint(A,B, guardianAngel.fxDistance) trigger.action.explosion(xP, guardianAngel.explosion) end @@ -226,7 +245,7 @@ function guardianAngel.monitorItem(theItem) guardianAngel.invokeCallbacks("intervention", theItem.targetName, theItem.weaponName) w:destroy() if guardianAngel.explosion > 0 then - local xP = guardianAngel.calcSafeExplosionPoint(A,B, 500) + local xP = guardianAngel.calcSafeExplosionPoint(A,B, guardianAngel.fxDistance) trigger.action.explosion(xP, guardianAngel.explosion) end return false -- remove from list @@ -415,6 +434,54 @@ function guardianAngel.somethingHappened(event) return end + if ID == 2 then + if not guardianAngel.intervention then return end -- we don't intervene + if not event.weapon then return end -- no weapon, no interest + local theWeapon = event.weapon + local wName = theWeapon:getName() + local theTarget = event.target + if not theTarget then return end -- should not happen, better safe then dead + local tName = theTarget:getName() + + local theProtegee = nil + for idx, aProt in pairs(guardianAngel.unitsToWatchOver) do + if tName == aProt:getName() then + theProtegee = aProt + end + end + + if not theProtegee then return end + + -- one of our protegees was hit + --trigger.action.outText("+++gA: Protegee " .. tName .. " was hit", 30) + trigger.action.outText("+++gA: I:" .. theUnit:getName() .. " hit " .. tName .. " with " .. wName, 30) + -- let's see if the victim was in our list of protected + -- units + local thePerp = nil + for idx, anItem in pairs(guardianAngel.missilesInTheAir) do + if anItem.weaponName == wName then + thePerp = anItem + end + end + + if not thePerp then return end + --trigger.action.outText("+++gA: offender was known to gA: " .. wName, 30) + + -- stats only: do target and intended target match? + local theWTarget = theWeapon:getTarget() + if not theWTarget then return end -- no target no interest + local wtName = theWTarget:getName() + if wtName == tName then + trigger.action.outText("+++gA: perp's ill intent confirmed", 30) + else + trigger.action.outText("+++gA: UNINTENDED CONSEQUENCES", 30) + end + + -- if we should have protected: mea maxima culpa + trigger.action.outText("[+++gA: Angel hangs her head in shame. Mea Culpa, " .. tName.."]", 30) + return + end + local myType = theUnit:getTypeName() if guardianAngel.verbose then trigger.action.outText("+++gA: event " .. ID .. " for unit " .. theUnit:getName() .. " of type " .. myType, 30) @@ -469,6 +536,7 @@ function guardianAngel.readConfigZone() guardianAngel.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true) guardianAngel.private = cfxZones.getBoolFromZoneProperty(theZone, "private", false) guardianAngel.explosion = cfxZones.getNumberFromZoneProperty(theZone, "explosion", -1) + guardianAngel.fxDistance = cfxZones.getNumberFromZoneProperty(theZone, "fxDistance", 500) end diff --git a/modules/unitZone.lua b/modules/unitZone.lua index a616876..11f0093 100644 --- a/modules/unitZone.lua +++ b/modules/unitZone.lua @@ -155,7 +155,7 @@ function unitZone.checkZoneStatus(theZone) end else - -- we perform group cehck + -- we perform group check for idx, aGroup in pairs(theGroups) do local gName=aGroup:getName() local hasMatch = false diff --git a/tutorial & demo missions/demo - CSAR of Georgia.miz b/tutorial & demo missions/demo - CSAR of Georgia.miz new file mode 100644 index 0000000..b103d74 Binary files /dev/null and b/tutorial & demo missions/demo - CSAR of Georgia.miz differ diff --git a/tutorial & demo missions/demo - missile evasion (guardian angel).miz b/tutorial & demo missions/demo - missile evasion (guardian angel).miz index 4283fab..18f501f 100644 Binary files a/tutorial & demo missions/demo - missile evasion (guardian angel).miz and b/tutorial & demo missions/demo - missile evasion (guardian angel).miz differ diff --git a/tutorial & demo missions/demo - track this!.miz b/tutorial & demo missions/demo - track this!.miz new file mode 100644 index 0000000..388a11c Binary files /dev/null and b/tutorial & demo missions/demo - track this!.miz differ diff --git a/tutorial & demo missions/full - Island Defender.miz b/tutorial & demo missions/full - Island Defender.miz index 5d930e4..54c9d4c 100644 Binary files a/tutorial & demo missions/full - Island Defender.miz and b/tutorial & demo missions/full - Island Defender.miz differ