Version 2.4.2

Player Score Update, Guardian Angel Update, new planeGuard
This commit is contained in:
Christian Franz 2025-01-29 07:18:10 +01:00
parent 4e78dfcb65
commit 3a87f7b784
22 changed files with 26464 additions and 24787 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,5 +1,5 @@
cfxMX = {} cfxMX = {}
cfxMX.version = "3.0.0" cfxMX.version = "3.0.1"
cfxMX.verbose = false cfxMX.verbose = false
--[[-- --[[--
Mission data decoder. Access to ME-built mission structures Mission data decoder. Access to ME-built mission structures
@ -22,6 +22,7 @@ cfxMX.verbose = false
3.0.0 - patch coalition.addGroup() to build unit table for wasUnit 3.0.0 - patch coalition.addGroup() to build unit table for wasUnit
- pre-populate spawnedUnits coa, cat from MX - pre-populate spawnedUnits coa, cat from MX
- spawnedUnitGroupNameByName - spawnedUnitGroupNameByName
3.0.1 - new getClosestUnitToPoint()
--]]-- --]]--
@ -33,7 +34,7 @@ cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {} cfxMX.groupIDbyName = {}
cfxMX.unitIDbyName = {} cfxMX.unitIDbyName = {}
cfxMX.groupCatByName = {} cfxMX.groupCatByName = {}
cfxMX.groupDataByName = {} cfxMX.groupDataByName = {} -- includes static groups!
cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"... cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"...
cfxMX.groupCoalitionByName = {} cfxMX.groupCoalitionByName = {}
cfxMX.groupHotByName = {} cfxMX.groupHotByName = {}
@ -413,6 +414,34 @@ function cfxMX.allGroupsInZoneByData(theZone, cat) -- returns groups indexed by
return theGroupsInZone, count return theGroupsInZone, count
end end
function cfxMX.getClosestUnitToPoint(A, filter) -- uses MX!
if not A then return nil end
if not filter then filter = "all" end
local theUnit = nil
local uIdx = nil
local theGroup = nil
local ax = A.x
local az = A.z
local closest = math.huge
for name, gData in pairs(cfxMX.groupDataByName) do
if filter == "all" or filter == cfxMX.groupTypeByName[name] then
for idx, uData in pairs(gData.units) do
-- use square delta, immediate
local dx = ax - uData.x
local dz = az - uData.y -- !!
local d = dx * dx + dz * dz
if d < closest then
theUnit = uData
theGroup = gData
closest = d
uIdx = idx
end
end
end
end
return theUnit, theGroup, uIdx, closest^0.5
end
function cfxMX.isDynamicPlayer(theUnit) function cfxMX.isDynamicPlayer(theUnit)
if not theUnit then return false end if not theUnit then return false end
if not theUnit.getName then return false end if not theUnit.getName then return false end

View File

@ -1,23 +1,21 @@
delayFlag = {} delayFlag = {}
delayFlag.version = "2.0.0" delayFlag.version = "2.1.0"
delayFlag.verbose = false delayFlag.verbose = false
delayFlag.requiredLibs = { delayFlag.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
delayFlag.flags = {} delayFlag.flags = {}
--[[-- --[[--
delay flags - simple flag switch & delay, allows for randomize delay flags - simple flag switch & delay, allows for randomize
and dead man switching and dead man switching
Copyright (c) 2022-2024 by Christian Franz and cf/x AG Copyright (c) 2022-2025 by Christian Franz and cf/x AG
Version History Version History
1.4.0 - dmlZones
- delayLeft#
2.0.0 - clean-up 2.0.0 - clean-up
2.1.0 - deprecated old attributes
- QoL: warnings when inputs/outputs missing
--]]-- --]]--
function delayFlag.addDelayZone(theZone) function delayFlag.addDelayZone(theZone)
@ -31,98 +29,73 @@ function delayFlag.getDelayZoneByName(aName)
if delayFlag.verbose then if delayFlag.verbose then
trigger.action.outText("+++dlyF: no delay flag with name <" .. aName ..">", 30) trigger.action.outText("+++dlyF: no delay flag with name <" .. aName ..">", 30)
end end
return nil return nil
end end
-- --
-- read attributes -- read attributes
-- --
--
-- create rnd gen from zone
--
function delayFlag.createTimerWithZone(theZone) function delayFlag.createTimerWithZone(theZone)
-- delay -- delay
theZone.delayMin, theZone.delayMax = theZone:getPositiveRangeFromZoneProperty("timeDelay", 1) -- same as zone signature theZone.delayMin, theZone.delayMax = theZone:getPositiveRangeFromZoneProperty("timeDelay", 1) -- same as zone signature
if delayFlag.verbose or theZone.verbose then if delayFlag.verbose or theZone.verbose then
trigger.action.outText("+++dlyF: time delay is <" .. theZone.delayMin .. ", " .. theZone.delayMax .. "> seconds", 30) trigger.action.outText("+++dlyF: time delay is <" .. theZone.delayMin .. ", " .. theZone.delayMax .. "> seconds", 30)
end end
-- watchflags:
-- triggerMethod -- triggerMethod
theZone.delayTriggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change") theZone.delayTriggerMethod = theZone:getStringFromZoneProperty("triggerMethod", "change")
if theZone:hasProperty("delayTriggerMethod") then if theZone:hasProperty("delayTriggerMethod") then
theZone.delayTriggerMethod = theZone:getStringFromZoneProperty("delayTriggerMethod", "change") theZone.delayTriggerMethod = theZone:getStringFromZoneProperty("delayTriggerMethod", "change")
end end
-- trigger flag -- trigger flag
if theZone:hasProperty("f?") then if theZone:hasProperty("startDelay?") then
theZone.triggerDelayFlag = theZone:getStringFromZoneProperty("f?", "none")
elseif theZone:hasProperty("in?") then
theZone.triggerDelayFlag = theZone:getStringFromZoneProperty("in?", "none")
elseif theZone:hasProperty("startDelay?") then
theZone.triggerDelayFlag = theZone:getStringFromZoneProperty("startDelay?", "none") theZone.triggerDelayFlag = theZone:getStringFromZoneProperty("startDelay?", "none")
end
if theZone.triggerDelayFlag then
theZone.lastDelayTriggerValue = theZone:getFlagValue(theZone.triggerDelayFlag) theZone.lastDelayTriggerValue = theZone:getFlagValue(theZone.triggerDelayFlag)
else
trigger.action.outText("+++dealyFlag: WARNING - zone <" .. theZone.name .. "> has no input connected.", 30)
end end
theZone.delayMethod = theZone:getStringFromZoneProperty("method", "inc") theZone.delayMethod = theZone:getStringFromZoneProperty("method", "inc")
if theZone:hasProperty("delayMethod") then if theZone:hasProperty("delayMethod") then
theZone.delayMethod = theZone:getStringFromZoneProperty( "delayMethod", "inc") theZone.delayMethod = theZone:getStringFromZoneProperty( "delayMethod", "inc")
end end
-- out flag -- out flag
theZone.delayDoneFlag = theZone:getStringFromZoneProperty("out!", "*<none>")
if theZone:hasProperty("delayDone!") then if theZone:hasProperty("delayDone!") then
theZone.delayDoneFlag = theZone:getStringFromZoneProperty( "delayDone!", "*<none>") theZone.delayDoneFlag = theZone:getStringFromZoneProperty( "delayDone!", "*<none>")
else
trigger.action.outText("+++delayFlag: WARNING - zone <" .. theZone.name .. "> has no output connected.", 30)
end end
-- stop the press! -- stop the press!
if theZone:hasProperty("stopDelay?") then if theZone:hasProperty("stopDelay?") then
theZone.triggerStopDelay = theZone:getStringFromZoneProperty("stopDelay?", "none") theZone.triggerStopDelay = theZone:getStringFromZoneProperty("stopDelay?", "none")
theZone.lastTriggerStopValue = theZone:getFlagValue(theZone.triggerStopDelay) theZone.lastTriggerStopValue = theZone:getFlagValue(theZone.triggerStopDelay)
end end
-- pause and continue -- pause and continue
if theZone:hasProperty("pauseDelay?") then if theZone:hasProperty("pauseDelay?") then
theZone.triggerPauseDelay = theZone:getStringFromZoneProperty("pauseDelay?", "none") theZone.triggerPauseDelay = theZone:getStringFromZoneProperty("pauseDelay?", "none")
theZone.lastTriggerPauseValue = theZone:getFlagValue(theZone.triggerPauseDelay) theZone.lastTriggerPauseValue = theZone:getFlagValue(theZone.triggerPauseDelay)
end end
if theZone:hasProperty("continueDelay?") then if theZone:hasProperty("continueDelay?") then
theZone.triggerContinueDelay = theZone:getStringFromZoneProperty("continueDelay?", "none") theZone.triggerContinueDelay = theZone:getStringFromZoneProperty("continueDelay?", "none")
theZone.lastTriggerContinueValue = theZone:getFlagValue(theZone.triggerContinueDelay) theZone.lastTriggerContinueValue = theZone:getFlagValue(theZone.triggerContinueDelay)
end end
-- timeInfo -- timeInfo
if theZone:hasProperty("delayLeft") then if theZone:hasProperty("delayLeft") then -- deprecated
theZone.delayTimeLeft = theZone:getStringFromZoneProperty("delayLeft", "*cfxIgnored") theZone.delayTimeLeft = theZone:getStringFromZoneProperty("delayLeft", "*cfxIgnored")
else else
theZone.delayTimeLeft = theZone:getStringFromZoneProperty("delayLeft#", "*cfxIgnored") theZone.delayTimeLeft = theZone:getStringFromZoneProperty("delayLeft#", "*cfxIgnored")
end end
-- init -- init
theZone.delayRunning = false theZone.delayRunning = false
theZone.delayPaused = false theZone.delayPaused = false
theZone.timeLimit = -1 -- current trigger time as calculated relative to getTime() theZone.timeLimit = -1 -- current trigger time as calculated relative to getTime()
theZone.timeLeft = -1 -- in seconds, always kept up to date theZone.timeLeft = -1 -- in seconds, always kept up to date
-- but not really used
theZone:setFlagValue(theZone.delayTimeLeft, -1) theZone:setFlagValue(theZone.delayTimeLeft, -1)
end end
-- --
-- update -- update
-- --
function delayFlag.startDelay(theZone) function delayFlag.startDelay(theZone)
-- refresh timer -- refresh timer
theZone.delayRunning = true theZone.delayRunning = true
@ -139,12 +112,10 @@ function delayFlag.startDelay(theZone)
delay = delay + delayMin delay = delay + delayMin
if delay > theZone.delayMax then delay = theZone.delayMax end if delay > theZone.delayMax then delay = theZone.delayMax end
if delay < 1 then delay = 1 end if delay < 1 then delay = 1 end
if delayFlag.verbose or theZone.verbose then if delayFlag.verbose or theZone.verbose then
trigger.action.outText("+++dlyF: delay " .. theZone.name .. " range " .. delayMin .. "-" .. delayMax .. ": selected " .. delay, 30) trigger.action.outText("+++dlyF: delay " .. theZone.name .. " range " .. delayMin .. "-" .. delayMax .. ": selected " .. delay, 30)
end end
end end
theZone.timeLimit = timer.getTime() + delay theZone.timeLimit = timer.getTime() + delay
theZone:setFlagValue(theZone.delayTimeLeft, delay) theZone:setFlagValue(theZone.delayTimeLeft, delay)
end end
@ -164,14 +135,11 @@ end
function delayFlag.update() function delayFlag.update()
-- call me in a second to poll triggers -- call me in a second to poll triggers
timer.scheduleFunction(delayFlag.update, {}, timer.getTime() + 1) timer.scheduleFunction(delayFlag.update, {}, timer.getTime() + 1)
local now = timer.getTime() local now = timer.getTime()
for idx, aZone in pairs(delayFlag.flags) do for idx, aZone in pairs(delayFlag.flags) do
-- calculate remaining time on the timer -- calculate remaining time on the timer
local remaining = aZone.timeLimit - now local remaining = aZone.timeLimit - now
if remaining < 0 then remaining = -1 end if remaining < 0 then remaining = -1 end
-- see if we need to stop -- see if we need to stop
if aZone:testZoneFlag(aZone.triggerStopDelay, aZone.delayTriggerMethod, "lastTriggerStopValue") then if aZone:testZoneFlag(aZone.triggerStopDelay, aZone.delayTriggerMethod, "lastTriggerStopValue") then
aZone.delayRunning = false -- simply stop. aZone.delayRunning = false -- simply stop.
@ -179,8 +147,6 @@ function delayFlag.update()
trigger.action.outText("+++dlyF: stopped delay " .. aZone.name, 30) trigger.action.outText("+++dlyF: stopped delay " .. aZone.name, 30)
end end
end end
if aZone:testZoneFlag(aZone.triggerDelayFlag, aZone.delayTriggerMethod, "lastDelayTriggerValue") then if aZone:testZoneFlag(aZone.triggerDelayFlag, aZone.delayTriggerMethod, "lastDelayTriggerValue") then
if delayFlag.verbose or aZone.verbose then if delayFlag.verbose or aZone.verbose then
if aZone.delayRunning then if aZone.delayRunning then
@ -194,14 +160,12 @@ function delayFlag.update()
end end
if not aZone.delayPaused then if not aZone.delayPaused then
if aZone.delayRunning and aZone:testZoneFlag( aZone.triggerPauseDelay, aZone.delayTriggerMethod, "lastTriggerPauseValue") then if aZone.delayRunning and aZone:testZoneFlag( aZone.triggerPauseDelay, aZone.delayTriggerMethod, "lastTriggerPauseValue") then
if delayFlag.verbose or aZone.verbose then if delayFlag.verbose or aZone.verbose then
trigger.action.outText("+++dlyF: pausing timer <" .. aZone.name .. "> with <" .. remaining .. "> remaining", 30) trigger.action.outText("+++dlyF: pausing timer <" .. aZone.name .. "> with <" .. remaining .. "> remaining", 30)
end end
delayFlag.pauseDelay(aZone) delayFlag.pauseDelay(aZone)
end end
if aZone.delayRunning then if aZone.delayRunning then
-- check expiry -- check expiry
if remaining < 0 then --now > aZone.timeLimit then if remaining < 0 then --now > aZone.timeLimit then
@ -214,7 +178,6 @@ function delayFlag.update()
aZone:pollFlag(aZone.delayDoneFlag, aZone.delayMethod) aZone:pollFlag(aZone.delayDoneFlag, aZone.delayMethod)
end end
end end
aZone:setFlagValue(aZone.delayTimeLeft, remaining) aZone:setFlagValue(aZone.delayTimeLeft, remaining)
else else
-- we are paused. Check for 'continue' -- we are paused. Check for 'continue'
@ -228,8 +191,6 @@ function delayFlag.update()
end end
end end
-- --
-- LOAD / SAVE -- LOAD / SAVE
-- --
@ -247,7 +208,6 @@ function delayFlag.saveData()
allTimers[theName] = timerData allTimers[theName] = timerData
end end
theData.allTimers = allTimers theData.allTimers = allTimers
return theData return theData
end end
@ -260,7 +220,6 @@ function delayFlag.loadData()
end end
return return
end end
local allTimers = theData.allTimers local allTimers = theData.allTimers
if not allTimers then if not allTimers then
if delayFlag.verbose then if delayFlag.verbose then
@ -268,7 +227,6 @@ function delayFlag.loadData()
end end
return return
end end
local now = timer.getTime() local now = timer.getTime()
for theName, theData in pairs(allTimers) do for theName, theData in pairs(allTimers) do
local theTimer = delayFlag.getDelayZoneByName(theName) local theTimer = delayFlag.getDelayZoneByName(theName)
@ -293,16 +251,9 @@ function delayFlag.readConfigZone()
if not theZone then if not theZone then
theZone = cfxZones.createSimpleZone("delayFlagsConfig") theZone = cfxZones.createSimpleZone("delayFlagsConfig")
end end
delayFlag.verbose = theZone.verbose delayFlag.verbose = theZone.verbose
if delayFlag.verbose then
trigger.action.outText("+++dlyF: read config", 30)
end
end end
function delayFlag.start() function delayFlag.start()
-- lib check -- lib check
if not dcsCommon.libCheck then if not dcsCommon.libCheck then
@ -313,17 +264,14 @@ function delayFlag.start()
delayFlag.requiredLibs) then delayFlag.requiredLibs) then
return false return false
end end
-- read config -- read config
delayFlag.readConfigZone() delayFlag.readConfigZone()
-- process cloner Zones -- process cloner Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("timeDelay") local attrZones = cfxZones.getZonesWithAttributeNamed("timeDelay")
for k, aZone in pairs(attrZones) do for k, aZone in pairs(attrZones) do
delayFlag.createTimerWithZone(aZone) -- process attributes delayFlag.createTimerWithZone(aZone) -- process attributes
delayFlag.addDelayZone(aZone) -- add to list delayFlag.addDelayZone(aZone) -- add to list
end end
-- load any saved data -- load any saved data
if persistence then if persistence then
-- sign up for persistence -- sign up for persistence
@ -333,10 +281,8 @@ function delayFlag.start()
-- now load my data -- now load my data
delayFlag.loadData() delayFlag.loadData()
end end
-- start update -- start update
delayFlag.update() delayFlag.update()
trigger.action.outText("cfx Delay Flag v" .. delayFlag.version .. " started.", 30) trigger.action.outText("cfx Delay Flag v" .. delayFlag.version .. " started.", 30)
return true return true
end end

View File

@ -1,84 +1,37 @@
guardianAngel = {} guardianAngel = {}
guardianAngel.version = "3.0.6" guardianAngel.version = "4.0.0"
guardianAngel.ups = 10 guardianAngel.ups = 10 -- hard-coded!! missile track
guardianAngel.name = "Guardian Angel" -- just in case someone accesses .name guardianAngel.name = "Guardian Angel" -- just in case someone accesses .name
guardianAngel.launchWarning = true -- detect launches and warn pilot
guardianAngel.intervention = true -- remove missiles just before hitting
guardianAngel.explosion = -1 -- small poof when missile explodes. -1 = off.
guardianAngel.verbose = false -- debug info
guardianAngel.announcer = true -- angel talks to you
guardianAngel.private = false -- angel only talks to group
guardianAngel.autoAddPlayers = true
guardianAngel.active = true -- can be turned on / off
guardianAngel.angelicZones = {}
guardianAngel.requiredLibs = { guardianAngel.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
} }
-- Guardian Angel DML script (c) 2021-2025 by Charistian Franz
--[[-- --[[--
Version History Version History
1.0.0 - Initial version 4.0.0 - code cleanup
2.0.0 - autoAddPlayer - DCS bug hardening
- verbose - guardianAngel.autoAddPlayer implemented
- lib check - removed preProcessor
- config zone - removed postproc
- sneaky detection logic - removed isInterestingEvent
- god intervention 100m - wired directly to onEvent
- detect re-acquisition - removed event id 20 (player enter)
- addUnitToWatch supports string names - mild performance tuning
- detect non-miss - new sanctuary zones
- announcer - sancturay zones have floor and ceiling
- intervene optional - once per second sanctuary calc
- launch warning optional - expanded getWatchedUnitByName to include inSanctuary
2.0.1 - warnings go to group - sanctuary zones use coalition
- invokeCallbacks added to module
- reworked CB structure
- private option
2.0.2 - poof! explosion option to show explosion on intervention
- can be dangerous
2.0.3 - fxDistance
- mea cupa capability
3.0.0 - on/off and switch monitoring
- active flag
- zones to designate protected aircraft
- zones to designate unprotected aircraft
- improved gA logging
- missilesAndTargets log
- re-targeting detection
- removed bubble check
- retarget Item code
- hardened missile disappear code
- all missiles are now tracked regardless whom they aim for
- removed item.wp
3.0.1 - corrected error on collateral (missing delay)
- Supporst cloned units
- removed legacy code
3.0.2 - added guardianAngel.name for those who use local flags on activate
3.0.3 - monitorItem() guards against loss of target (nil)
3.0.4 - launchSound attribute
- interventionSound attribute
3.0.5 - better missiole names, their object IDs seem to have disappeared, also storing launcher name
- msgTime to control how long warnings remain on the screen
- disappear message now only on verbose
- dmlZones
3.0.6 - Hardening of targets that aren't part of groups
This script detects missiles launched against protected aircraft an
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.active = true -- can be turned on / off
guardianAngel.myEvents = {1, 15, 20, 21, 23, 2} -- 1 - shot, 15 - birth, 20 - enter unit, 21 - player leave unit, 23 - start shooting guardianAngel.angelicZones = {} -- angels be active here
-- added 2 (hit) event to see if angel was defeated guardianAngel.sanctums = {} -- and here, but only as temps
guardianAngel.minMissileDist = 50 -- m. below this distance the missile removed
guardianAngel.safetyFactor = 1.8 -- for calculating dealloc range guardianAngel.safetyFactor = 1.8 -- for calculating dealloc range
guardianAngel.unitsToWatchOver = {} -- I'll watch over these guardianAngel.unitsToWatchOver = {} -- I'll watch over these units
guardianAngel.inSanctuary = {} -- temporarily protected
guardianAngel.missilesInTheAir = {} -- missiles in the air guardianAngel.missilesInTheAir = {} -- missiles in the air
guardianAngel.missilesAndTargets = {} -- permanent log which missile was aimed at whom guardianAngel.missilesAndTargets = {} -- permanent log which missile was aimed at whom
guardianAngel.callBacks = {} -- callbacks guardianAngel.callBacks = {} -- callbacks
@ -96,45 +49,35 @@ function guardianAngel.invokeCallbacks(reason, targetName, weaponName)
theCB(reason, targetName, weaponName) theCB(reason, targetName, weaponName)
end end
end end
-- --
-- units to watch -- units to watch (permanent protection list)
-- --
function guardianAngel.addUnitToWatch(aUnit) function guardianAngel.addUnitToWatch(aUnit)
if type(aUnit) == "string" then
aUnit = Unit.getByName(aUnit)
end
if not aUnit then return end if not aUnit then return end
local unitName = aUnit:getName() local unitName = aUnit:getName()
local isNew = guardianAngel.unitsToWatchOver[unitName] == nil local isNew = guardianAngel.unitsToWatchOver[unitName] == nil
guardianAngel.unitsToWatchOver[unitName] = aUnit guardianAngel.unitsToWatchOver[unitName] = aUnit
if guardianAngel.verbose then if guardianAngel.verbose then
if isNew then if isNew then trigger.action.outText("+++gA: now guarding unit " .. unitName, 30)
trigger.action.outText("+++gA: now watching unit " .. unitName, 30) else trigger.action.outText("+++gA: updating unit " .. unitName, 30)
else
trigger.action.outText("+++gA: updating unit " .. unitName, 30)
end end
end end
end end
function guardianAngel.removeUnitToWatch(aUnit) function guardianAngel.removeUnitToWatch(aUnit)
if type(aUnit) == "string" then
aUnit = Unit.getByName(aUnit)
end
if not aUnit then return end if not aUnit then return end
local unitName = aUnit:getName() local unitName = aUnit:getName()
if not unitName then return end if not unitName then return end
guardianAngel.unitsToWatchOver[unitName] = nil guardianAngel.unitsToWatchOver[unitName] = nil
if guardianAngel.verbose then if guardianAngel.verbose then trigger.action.outText("+++gA: no longer watching " .. aUnit:getName(), 30) end
trigger.action.outText("+++gA: no longer watching " .. aUnit:getName(), 30)
end
end end
function guardianAngel.getWatchedUnitByName(aName) function guardianAngel.getWatchedUnitByName(aName)
if not aName then return nil end if not aName then return nil end
return guardianAngel.unitsToWatchOver[aName] local u = guardianAngel.unitsToWatchOver[aName]
if u then return u end -- always protected
return guardianAngel.inSanctuary[aName] -- returns unit or nil
end end
-- --
-- watch q items -- watch q items
-- --
@ -145,28 +88,22 @@ function guardianAngel.createQItem(theWeapon, theTarget, threat, launcher)
if not threat then threat = false end if not threat then threat = false end
-- if an item is not a 'threat' it means that we merely -- if an item is not a 'threat' it means that we merely
-- watch it for re-targeting purposes -- watch it for re-targeting purposes
local theItem = {} local theItem = {}
local oName = tostring(theWeapon:getName()) local oName = tostring(theWeapon:getName())
if not oName or #oName < 1 then oName = dcsCommon.numberUUID() end if not oName or #oName < 1 then oName = dcsCommon.numberUUID() end
local wName = "" local wName = ""
if theWeapon.getDisplayName then if theWeapon.getDisplayName then
wName = theWeapon:getDisplayName() -- does this even exist any more? wName = theWeapon:getDisplayName() -- does this even exist any more?
elseif theWeapon.getTypeName then elseif theWeapon.getTypeName then wName = theWeapon:getTypeName()
wName = theWeapon:getTypeName() else wName = "<Generic>" end
else
wName = "<Generic>"
end
wName = wName .. "-" .. oName wName = wName .. "-" .. oName
local launcherName = launcher:getTypeName() .. " " .. launcher:getName() local launcherName = launcher:getTypeName() .. " " .. launcher:getName()
theItem.theWeapon = theWeapon -- weapon that we are tracking theItem.theWeapon = theWeapon -- weapon that we are tracking
theItem.weaponName = wName -- theWeapon:getName() theItem.weaponName = wName -- theWeapon:getName()
-- usually weapons have no 'name' except an ID, so let's get the -- usually weapons have no 'name' except an ID, so let's get
-- type or display name. Weapons often have no display name. -- type/display name. Weapons often have no display name.
if guardianAngel.verbose then if guardianAngel.verbose then trigger.action.outText("gA: tracking missile <" .. wName .. "> launched by <" .. launcherName .. ">", guardianAngel.msgTime) end
trigger.action.outText("gA: tracking missile <" .. wName .. "> launched by <" .. launcherName .. ">", guardianAngel.msgTime)
end
theItem.theTarget = theTarget theItem.theTarget = theTarget
if theTarget.getGroup then -- some targets may not have a group if theTarget.getGroup then -- some targets may not have a group
theItem.tGroup = theTarget:getGroup() theItem.tGroup = theTarget:getGroup()
@ -202,16 +139,12 @@ function guardianAngel.retargetItem(theItem, theTarget, threat)
if not threat then threat = false end if not threat then threat = false end
theItem.timeStamp = timer.getTime() theItem.timeStamp = timer.getTime()
theItem.threat = threat theItem.threat = threat
theItem.theTarget = theTarget theItem.theTarget = theTarget
if not theTarget.getGroup then if not theTarget.getGroup then
local theCat = theTarget:getCategory() local theCat = theTarget:getCategory()
if theCat ~= 2 then if theCat ~= 2 then
-- not a weapon / flare -- not a weapon / flare
trigger.action.outText("*** gA: WARNING: <" .. theTarget:getName() .. "> has no getGroup and is of category <" .. theCat .. ">!!!", 30) trigger.action.outText("*** gA: WARNING: <" .. theTarget:getName() .. "> has no getGroup and is of category <" .. theCat .. ">!!!", 30)
else
-- target is a weapon (flare/chaff/decoy), all is well
end end
else else
theItem.tGroup = theTarget:getGroup() theItem.tGroup = theTarget:getGroup()
@ -219,16 +152,13 @@ function guardianAngel.retargetItem(theItem, theTarget, threat)
end end
theItem.targetName = theTarget:getName() theItem.targetName = theTarget:getName()
theItem.lastDistance = math.huge theItem.lastDistance = math.huge
--theItem.lostTrack = false
theItem.missed = false theItem.missed = false
theItem.lastDesc = "(retarget)" theItem.lastDesc = "(retarget)"
end end
function guardianAngel.getQItemForWeaponNamed(theName) function guardianAngel.getQItemForWeaponNamed(theName)
for idx, theItem in pairs (guardianAngel.missilesInTheAir) do for idx, theItem in pairs (guardianAngel.missilesInTheAir) do
if theItem.weaponName == theName then if theItem.weaponName == theName then return theItem end
return theItem
end
end end
return nil return nil
end end
@ -239,11 +169,9 @@ function guardianAngel.calcSafeExplosionPoint(wpn, pln, dist)
local v = dcsCommon.vNorm(dirToWpn) -- |v| = 1 local v = dcsCommon.vNorm(dirToWpn) -- |v| = 1
local v = dcsCommon.vMultScalar(v, dist) -- |v| = dist local v = dcsCommon.vMultScalar(v, dist) -- |v| = dist
local newPoint = dcsCommon.vAdd(pln, v) local newPoint = dcsCommon.vAdd(pln, v)
--trigger.action.outText("+++ gA: safe dist is ".. dist, 30)
return newPoint return newPoint
end end
function guardianAngel.monitorItem(theItem) function guardianAngel.monitorItem(theItem)
local w = theItem.theWeapon local w = theItem.theWeapon
local ID = theItem.tID local ID = theItem.tID
@ -258,31 +186,14 @@ function guardianAngel.monitorItem(theItem)
local t = theItem.theTarget local t = theItem.theTarget
local currentTarget = w:getTarget() local currentTarget = w:getTarget()
-- Re-target check. did missile pick a new target? -- Re-target check. did missile pick a new target?
-- this can happen with any missile, even threat missiles, local ctName = "***guardianAngel.not.set"
-- so do this always! if currentTarget and Object.isExist(currentTarget) then ctName = currentTarget:getName() end
local ctName = nil if ctName ~= theItem.targetName then
if currentTarget and Object.isExist(currentTarget) then
-- get current name to check against last target name
ctName = currentTarget:getName()
else
-- currentTarget has disappeared, kill the 'threat flag'
-- theItem.threat = false
ctName = "***guardianangel.not.set"
end
if ctName and ctName ~= theItem.targetName then
if guardianAngel.verbose then
--trigger.action.outText("+++gA: RETARGETING for <" .. theItem.weaponName .. ">: from <" .. theItem.targetName .. "> to <" .. ctName .. ">", 30)
end
-- see if it's a threat to us now -- see if it's a threat to us now
local watchedUnit = guardianAngel.getWatchedUnitByName(ctName) local watchedUnit = guardianAngel.getWatchedUnitByName(ctName)
-- update the db who's seeking who -- update the db who's seeking who
guardianAngel.missilesAndTargets[theItem.weaponName] = ctName guardianAngel.missilesAndTargets[theItem.weaponName] = ctName
-- should now update theItem to new target info -- should now update theItem to new target info
isThreat = false isThreat = false
if guardianAngel.getWatchedUnitByName(ctName) then if guardianAngel.getWatchedUnitByName(ctName) then
@ -290,7 +201,6 @@ function guardianAngel.monitorItem(theItem)
if guardianAngel.verbose then if guardianAngel.verbose then
trigger.action.outText("+++gA: <" .. theItem.weaponName .. "> now targeting protected <" .. ctName .. ">!", 30) trigger.action.outText("+++gA: <" .. theItem.weaponName .. "> now targeting protected <" .. ctName .. ">!", 30)
end end
if isThreat and guardianAngel.announcer and guardianAngel.active then if isThreat and guardianAngel.announcer and guardianAngel.active then
local desc = "Missile, missile, missile - now heading for " .. ctName .. "!" local desc = "Missile, missile, missile - now heading for " .. ctName .. "!"
if guardianAngel.private and ID then if guardianAngel.private and ID then
@ -308,30 +218,20 @@ function guardianAngel.monitorItem(theItem)
end end
end end
end end
guardianAngel.retargetItem(theItem, currentTarget, isThreat) guardianAngel.retargetItem(theItem, currentTarget, isThreat)
t = currentTarget t = currentTarget
else
-- not ctName, or name as before.
-- go on.
end end
-- we only progress here is the missile is a threat. -- we only progress here is the missile is a threat.
-- if not, we keep it and check next time if it has -- if not, we keep it and check next time if it has
-- retargeted a protegee -- retargeted a protegee
if not theItem.threat then return true end if not theItem.threat then return true end
-- local oldWPos = theItem.wP
local A = w:getPoint() -- A is new point of weapon 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 -- new code: safety check with ALL protected wings
-- local bubbleThreat = guardianAngel.bubbleCheck(A, w) -- local bubbleThreat = guardianAngel.bubbleCheck(A, w)
-- safety check removed, no benefit after new code -- safety check removed, no benefit after new code
local B local B
if currentTarget then B = currentTarget:getPoint() else B = A end if currentTarget then B = currentTarget:getPoint() else B = A end
local d = math.floor(dcsCommon.dist(A, B)) local d = math.floor(dcsCommon.dist(A, B))
theItem.lastDistance = d -- save it for post mortem theItem.lastDistance = d -- save it for post mortem
local desc = theItem.weaponName .. ": " local desc = theItem.weaponName .. ": "
@ -339,22 +239,18 @@ function guardianAngel.monitorItem(theItem)
desc = desc .. "tracking " .. theItem.targetName .. ", d = " .. d .. "m" desc = desc .. "tracking " .. theItem.targetName .. ", d = " .. d .. "m"
local vcc = dcsCommon.getClosingVelocity(t, w) local vcc = dcsCommon.getClosingVelocity(t, w)
desc = desc .. ", Vcc = " .. math.floor(vcc) .. "m/s" desc = desc .. ", Vcc = " .. math.floor(vcc) .. "m/s"
-- calculate lethal distance: vcc is in meters per second
-- now calculate lethal distance: vcc is in meters per second -- we sample ups times per second
-- and we sample ups times per second -- making the missile move vcc / ups meters per
-- making the missile cover vcc / ups meters in the next -- timer interval. If it now is closer than that, we destroy msl
-- timer interval. If it now is closer than that, we have to
-- destroy the missile
local lethalRange = math.abs(vcc / guardianAngel.ups) * guardianAngel.safetyFactor local lethalRange = math.abs(vcc / guardianAngel.ups) * guardianAngel.safetyFactor
desc = desc .. ", LR= " .. math.floor(lethalRange) .. "m" desc = desc .. ", LR= " .. math.floor(lethalRange) .. "m"
theItem.lastDesc = desc theItem.lastDesc = desc
theItem.timeStamp = timer.getTime() theItem.timeStamp = timer.getTime()
if guardianAngel.intervention and if guardianAngel.intervention and
d <= lethalRange + 10 d <= lethalRange + 10
then then
desc = desc .. " ANGEL INTERVENTION" desc = desc .. " ANGEL INTERVENTION"
if guardianAngel.announcer and ID then if guardianAngel.announcer and ID then
if guardianAngel.private then if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime) trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
@ -372,24 +268,16 @@ function guardianAngel.monitorItem(theItem)
end end
guardianAngel.invokeCallbacks("intervention", theItem.targetName, theItem.weaponName) guardianAngel.invokeCallbacks("intervention", theItem.targetName, theItem.weaponName)
w:destroy() w:destroy()
-- now add some showy explosion so the missile doesn't just disappear
-- now add some showy explosion so the missile
-- doesn't just disappear
if guardianAngel.explosion > 0 then if guardianAngel.explosion > 0 then
local xP = guardianAngel.calcSafeExplosionPoint(A,B, guardianAngel.fxDistance) local xP = guardianAngel.calcSafeExplosionPoint(A,B, guardianAngel.fxDistance)
trigger.action.explosion(xP, guardianAngel.explosion) trigger.action.explosion(xP, guardianAngel.explosion)
end end
return false -- remove from list return false -- remove from list
end end
if guardianAngel.intervention and if guardianAngel.intervention and d <= guardianAngel.minMissileDist then -- god's override
d <= guardianAngel.minMissileDist -- god's override
then
desc = desc .. " GOD INTERVENTION" desc = desc .. " GOD INTERVENTION"
--if theItem.lostTrack then desc = desc .. " (little sneak!)" end
--if theItem.missed then desc = desc .. " (missed you!)" end
if guardianAngel.announcer and ID then if guardianAngel.announcer and ID then
if guardianAngel.private then if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime) trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
@ -405,26 +293,16 @@ function guardianAngel.monitorItem(theItem)
end end
return false -- remove from list return false -- remove from list
end end
else
end end
return true -- keep in list
return true
end end
function guardianAngel.monitorMissiles() function guardianAngel.monitorMissiles()
local newArray = {} -- we collect all still existing missiles here local newArray = {} -- monitor existing msl
-- and replace missilesInTheAir with that for next round
for idx, anItem in pairs (guardianAngel.missilesInTheAir) do for idx, anItem in pairs (guardianAngel.missilesInTheAir) do
-- we now have an item
-- see about detection
-- guardianAngel.detectItem(anItem)
-- see if the weapon is still in existence -- see if the weapon is still in existence
local stillAlive = guardianAngel.monitorItem(anItem) local stillAlive = guardianAngel.monitorItem(anItem)
if stillAlive then if stillAlive then table.insert(newArray, anItem) end
table.insert(newArray, anItem)
end
end end
guardianAngel.missilesInTheAir = newArray guardianAngel.missilesInTheAir = newArray
end end
@ -432,154 +310,60 @@ end
function guardianAngel.filterItem(theItem) function guardianAngel.filterItem(theItem)
local w = theItem.theWeapon local w = theItem.theWeapon
if not w then return false end if not w then return false end
if not w:isExist() then if not w:isExist() then return false end
return false
end
return true -- missile still alive return true -- missile still alive
end end
function guardianAngel.filterMissiles() function guardianAngel.filterMissiles()
local newArray = {} -- we collect all still existing missiles here local newArray = {} -- filter msl
-- and replace missilesInTheAir with that for next round
for idx, anItem in pairs (guardianAngel.missilesInTheAir) do for idx, anItem in pairs (guardianAngel.missilesInTheAir) do
-- we now have an item
-- see about detection
-- guardianAngel.detectItem(anItem)
-- see if the weapon is still in existence -- see if the weapon is still in existence
local stillAlive = guardianAngel.filterItem(anItem) local stillAlive = guardianAngel.filterItem(anItem)
if stillAlive then if stillAlive then table.insert(newArray, anItem) end
table.insert(newArray, anItem)
end
end end
guardianAngel.missilesInTheAir = newArray guardianAngel.missilesInTheAir = newArray
end end
-- --
-- E V E N T P R O C E S S I N G -- E V E N T P R O C E S S I N G
-- --
function guardianAngel.isInteresting(eventID)
-- return true if we are interested in this event, false else
for key, evType in pairs(guardianAngel.myEvents) do
if evType == eventID then return true end
end
return false
end
-- event pre-proc: only return true if we need to process this event
function guardianAngel.preProcessor(event)
-- all events must have initiator set
if not event.initiator then return false end
-- see if the event ID is interesting for us
local interesting = guardianAngel.isInteresting(event.id)
return interesting
end
function guardianAngel.postProcessor(event)
-- don't do anything for now
end
function guardianAngel.getAngelicZoneForUnit(theUnit) function guardianAngel.getAngelicZoneForUnit(theUnit)
for idx, theZone in pairs(guardianAngel.angelicZones) do for idx, theZone in pairs(guardianAngel.angelicZones) do
if cfxZones.unitInZone(theUnit, theZone) then if cfxZones.unitInZone(theUnit, theZone) then return theZone end
return theZone
end
end end
return nil return nil
end end
-- event callback from dcsCommon event handler. preProcessor has returned true function guardianAngel:onEvent(event)
function guardianAngel.somethingHappened(event) if not event.initiator then return end
-- when this is invoked, the preprocessor guarantees that
-- it's an interesting event and has initiator
local ID = event.id local ID = event.id
local theUnit = event.initiator local theUnit = event.initiator
-- make sure that this is a cat 0 or cat 1
local playerName = nil local playerName = nil
if theUnit.getPlayerName then if theUnit.getPlayerName then playerName = theUnit:getPlayerName() end
playerName = theUnit:getPlayerName() -- nil if not a player
end
local mustProtect = false local mustProtect = false
if ID == 15 and playerName then
-- this is a player created unit
if guardianAngel.verbose then
trigger.action.outText("+++gA: player unit born " .. theUnit:getName(), 30)
end
if guardianAngel.autoAddPlayers then
mustProtect = true
end
theZone = guardianAngel.getAngelicZoneForUnit(theUnit) if ID == 15 then
if theZone then -- AI/player spawn. check if it is an aircraft and in an angelic zone
mustProtect = theZone.angelic
if theZone.verbose or guardianAngel.verbose then
trigger.action.outText("+++gA: angelic zone " .. theZone.name .." -- protect: (" .. dcsCommon.bool2YesNo(mustProtect) .. ")", 30)
end
end
if mustProtect then
guardianAngel.addUnitToWatch(theUnit)
end
return
elseif ID == 15 then
-- AI spawn. check if it is an aircraft and in an angelic zone
-- docs say that initiator is object. so let's see if when we -- docs say that initiator is object. so let's see if when we
-- get cat, this returns 1 for unit (as it should, so we can get -- get cat, this returns 1 for unit (as it should, so we can get
-- group, or if it's really a unit, which returns 0 for aircraft -- group, or if it's really a unit, which returns 0 for aircraft
local cat = theUnit:getCategory() if not theUnit.getCategory then return end
--trigger.action.outText("birth event for " .. theUnit:getName() .. " with cat = " .. cat, 30)
if cat ~= 1 then
-- not a unit, bye bye
return
end
local theGroup = theUnit:getGroup() local theGroup = theUnit:getGroup()
if not theGroup then return end
local gCat = theGroup:getCategory() local gCat = theGroup:getCategory()
if gCat == 0 or gCat == 1 then if gCat ~= 0 and gCat ~= 1 then return end -- only fixed and rotor wing
--trigger.action.outText("is aircraft cat " .. gCat, 30)
theZone = guardianAngel.getAngelicZoneForUnit(theUnit)
if theZone then
mustProtect = theZone.angelic
if theZone.verbose or guardianAngel.verbose then
trigger.action.outText("+++gA: angelic zone <" .. theZone.name .."> contains unit <" .. theUnit:getName() .. ">, protect it: " .. dcsCommon.bool2YesNo(mustProtect) .. ".", 30)
end
end
if mustProtect then
guardianAngel.addUnitToWatch(theUnit)
end
end
return
end
if ID == 20 and playerName then
-- this is a player entered unit
if guardianAngel.verbose then
trigger.action.outText("+++gA: player seated in unit " .. theUnit:getName(), 30)
end
if guardianAngel.autoAddPlayers then
mustProtect = true
end
theZone = guardianAngel.getAngelicZoneForUnit(theUnit) theZone = guardianAngel.getAngelicZoneForUnit(theUnit)
if theZone then if theZone then
mustProtect = theZone.angelic mustProtect = theZone.angelic
if theZone.verbose or guardianAngel.verbose then if theZone.verbose or guardianAngel.verbose then
trigger.action.outText("+++gA: angelic zone " .. theZone.name .." -- protect: (" .. dcsCommon.bool2YesNo(mustProtect) .. ")", 30) trigger.action.outText("+++gA: angelic zone <" .. theZone.name .."> contains unit <" .. theUnit:getName() .. ">, protect it: " .. dcsCommon.bool2YesNo(mustProtect) .. ".", 30)
end end
end end
if playerName and guardianAngel.autoAddPlayer then mustProtect = true end
if mustProtect then if mustProtect then guardianAngel.addUnitToWatch(theUnit) end
guardianAngel.addUnitToWatch(theUnit)
end
return return
end end
if ID == 21 and playerName then if ID == 21 and playerName then -- player leave unit
guardianAngel.removeUnitToWatch(theUnit) guardianAngel.removeUnitToWatch(theUnit)
return return
end end
@ -589,7 +373,6 @@ function guardianAngel.somethingHappened(event)
return return
end end
if ID == 1 then if ID == 1 then
-- even if not active, we collect missile data -- even if not active, we collect missile data
-- someone shot something. see if it is fire directed at me -- someone shot something. see if it is fire directed at me
@ -597,12 +380,8 @@ function guardianAngel.somethingHappened(event)
local theTarget local theTarget
if theWeapon then if theWeapon then
theTarget = theWeapon:getTarget() theTarget = theWeapon:getTarget()
else else return end
return if not theTarget then return end
end
if not theTarget then
return
end
if not theTarget:isExist() then return end if not theTarget:isExist() then return end
-- if we get here, we have weapon aimed at a target -- if we get here, we have weapon aimed at a target
@ -611,17 +390,13 @@ function guardianAngel.somethingHappened(event)
local launcher = theUnit local launcher = theUnit
guardianAngel.missilesAndTargets[theWeapon:getName()] = targetName guardianAngel.missilesAndTargets[theWeapon:getName()] = targetName
if not watchedUnit then if not watchedUnit then
-- we may still want to watch this if the missile
-- can be re-targeted -- can be re-targeted
if guardianAngel.verbose then if guardianAngel.verbose then trigger.action.outText("+++gA: missile <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">, not a threat", 30) end
trigger.action.outText("+++gA: missile <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">, not a threat", 30)
end
-- add it as no threat -- add it as no threat
local theQItem = guardianAngel.createQItem(theWeapon, theTarget, false, launcher) -- this is not a threat, simply watch for re-target local theQItem = guardianAngel.createQItem(theWeapon, theTarget, false, launcher) -- this is not a threat, simply watch for re-target
table.insert(guardianAngel.missilesInTheAir, theQItem) table.insert(guardianAngel.missilesInTheAir, theQItem)
return return -- fired at some other poor sucker, we don't care
end -- fired at some other poor sucker, we don't care end
-- if we get here, someone fired a guided weapon at my watched units -- if we get here, someone fired a guided weapon at my watched units
-- create a new item for my queue -- create a new item for my queue
local theQItem = guardianAngel.createQItem(theWeapon, theTarget, true, launcher) -- this is watched local theQItem = guardianAngel.createQItem(theWeapon, theTarget, true, launcher) -- this is watched
@ -632,12 +407,9 @@ function guardianAngel.somethingHappened(event)
local A = theWeapon:getPoint() local A = theWeapon:getPoint()
local B = theTarget:getPoint() local B = theTarget:getPoint()
local oclock = dcsCommon.clockPositionOfARelativeToB(A, B, unitHeading) local oclock = dcsCommon.clockPositionOfARelativeToB(A, B, unitHeading)
local grpID = theTarget:getGroup():getID() local grpID = theTarget:getGroup():getID()
local vbInfo = "" local vbInfo = ""
if guardianAngel.verbose then if guardianAngel.verbose then vbInfo = ", <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">" end
vbInfo = ", <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">"
end
if guardianAngel.launchWarning and guardianAngel.active then if guardianAngel.launchWarning and guardianAngel.active then
-- currently, we always detect immediately -- currently, we always detect immediately
-- can be moved to update() -- can be moved to update()
@ -654,13 +426,12 @@ function guardianAngel.somethingHappened(event)
trigger.action.outSound(fileName) trigger.action.outSound(fileName)
end end
end end
theQItem.detected = true -- remember: we detected and warned already theQItem.detected = true -- remember: we detected and warned already
end end
return return
end end
if ID == 2 then if ID == 2 then -- hit
if not guardianAngel.active then return end -- we aren't on watch. if not guardianAngel.active then return end -- we aren't on watch.
if not guardianAngel.intervention then return end -- we don't intervene if not guardianAngel.intervention then return end -- we don't intervene
if not event.weapon then return end -- no weapon, no interest if not event.weapon then return end -- no weapon, no interest
@ -673,19 +444,14 @@ function guardianAngel.somethingHappened(event)
local theProtegee = nil local theProtegee = nil
for idx, aProt in pairs(guardianAngel.unitsToWatchOver) do for idx, aProt in pairs(guardianAngel.unitsToWatchOver) do
if aProt:isExist() then if aProt:isExist() then
if tName == aProt:getName() then if tName == aProt:getName() then theProtegee = aProt end
theProtegee = aProt
end
else else
if guardianAngel.verbose then if guardianAngel.verbose then trigger.action.outText("+++gA: Whoops. Looks like I lost a wing there... sorry", 30) end
trigger.action.outText("+++gA: whoops. Looks like I lost a wing there... sorry", 30)
end
end end
end end
if not theProtegee then return end if not theProtegee then return end
-- one of our protegees was hit -- 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) -- note: theUnit is the LAUNCHER or the weapon!!! trigger.action.outText("+++gA: I:" .. theUnit:getName() .. " hit " .. tName .. " with " .. wName, 30) -- note: theUnit is the LAUNCHER or the weapon!!!
if guardianAngel.missilesAndTargets[wName] and guardianAngel.verbose then if guardianAngel.missilesAndTargets[wName] and guardianAngel.verbose then
trigger.action.outText("+++gA: <" .. wName .. "> was originally aimed at <" .. guardianAngel.missilesAndTargets[wName] .. ">", 30) trigger.action.outText("+++gA: <" .. wName .. "> was originally aimed at <" .. guardianAngel.missilesAndTargets[wName] .. ">", 30)
@ -696,63 +462,49 @@ function guardianAngel.somethingHappened(event)
local wpnTgtName = "(none???)" local wpnTgtName = "(none???)"
if wpnTgt then wpnTgtName = wpnTgt:getName() end if wpnTgt then wpnTgtName = wpnTgt:getName() end
trigger.action.outText("+++gA: *current* weapon's target is <" .. wpnTgtName .. ">", 30) trigger.action.outText("+++gA: *current* weapon's target is <" .. wpnTgtName .. ">", 30)
if wpnTgtName ~= tName then if wpnTgtName ~= tName then trigger.action.outText("+++gA: COLLATERAL DAMAGE!", 30) end
trigger.action.outText("+++gA: COLLATERAL DAMAGE!", 30)
end
end end
else else
trigger.action.outText("***gA: no missile in the air for <" .. wName .. ">!!!!", 30) trigger.action.outText("***gA: no missile in the air for <" .. wName .. ">!!!!", 30)
end end
-- let's see if the victim was in our list of protected -- let's see if the victim was in our list of protected units
-- units
local thePerp = nil local thePerp = nil
for idx, anItem in pairs(guardianAngel.missilesInTheAir) do for idx, anItem in pairs(guardianAngel.missilesInTheAir) do
if anItem.weaponName == wName then if anItem.weaponName == wName then thePerp = anItem end
thePerp = anItem
end
end end
if not thePerp then return 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? -- stats only: do target and intended target match?
local theWTarget = theWeapon:getTarget() local theWTarget = theWeapon:getTarget()
if not theWTarget then return end -- no target no interest if not theWTarget then return end -- no target no interest
local wtName = theWTarget:getName() local wtName = theWTarget:getName()
if wtName == tName then if wtName == tName then trigger.action.outText("+++gA: perp's ill intent confirmed", 30)
trigger.action.outText("+++gA: perp's ill intent confirmed", 30) else trigger.action.outText("+++gA: UNINTENDED CONSEQUENCES", 30)
else
trigger.action.outText("+++gA: UNINTENDED CONSEQUENCES", 30)
end end
-- if we should have protected: mea maxima culpa -- if we should have protected: mea maxima culpa
trigger.action.outText("[+++gA: Angel hangs her head in shame. Mea Culpa, " .. tName.."]", 30) trigger.action.outText("[+++gA: Angel hangs her head in shame. Mea Culpa, " .. tName.."]", 30)
-- see if we can find the q item -- see if we can find the q item
local missedItem = guardianAngel.getQItemForWeaponNamed(wName) local missedItem = guardianAngel.getQItemForWeaponNamed(wName)
if not missedItem then if not missedItem then trigger.action.outText("Cannot retrieve item for <" .. wName .. ">", 30)
trigger.action.outText("Cannot retrieve item for <" .. wName .. ">", 30)
else else
local now = timer.getTime() local now = timer.getTime()
local delta = now - missedItem.timeStamp local delta = now - missedItem.timeStamp
local wasThreat = dcsCommon.bool2YesNo(missedItem.threat) local wasThreat = dcsCommon.bool2YesNo(missedItem.threat)
trigger.action.outText("postmortem: target was <" .. missedItem.targetName .. "> with last dist <" .. missedItem.lastDistance .. "> for weapon <" .. missedItem.weaponName .. ">, with dast desc = <" .. missedItem.lastDesc .. ">, <" .. delta .. "> s ago, Threat:(" .. wasThreat .. ")", 30)
trigger.action.outText("post: target was <" .. missedItem.targetName .. "> with last dist <" .. missedItem.lastDistance .. "> for weapon <" .. missedItem.weaponName .. ">, with dast desc = <" .. missedItem.lastDesc .. ">, <" .. delta .. "> s ago, Threat:(" .. wasThreat .. ")", 30)
end end
return return
end end
local myType = theUnit:getTypeName()
if guardianAngel.verbose then if guardianAngel.verbose then
local myType = theUnit:getTypeName()
trigger.action.outText("+++gA: event " .. ID .. " for unit " .. theUnit:getName() .. " of type " .. myType, 30) trigger.action.outText("+++gA: event " .. ID .. " for unit " .. theUnit:getName() .. " of type " .. myType, 30)
end end
end end
-- --
-- U P D A T E L O O P -- U P D A T E L O O P
-- --
function guardianAngel.update() function guardianAngel.update()
timer.scheduleFunction(guardianAngel.update, {}, timer.getTime() + 1/guardianAngel.ups) timer.scheduleFunction(guardianAngel.update, {}, timer.getTime() + 1/guardianAngel.ups)
-- and break off if nothing to do -- and break off if nothing to do
@ -760,53 +512,85 @@ function guardianAngel.update()
guardianAngel.filterMissiles() guardianAngel.filterMissiles()
return return
end end
guardianAngel.monitorMissiles() guardianAngel.monitorMissiles()
end end
function guardianAngel.doActivate() function guardianAngel.doActivate()
guardianAngel.active = true guardianAngel.active = true
if guardianAngel.verbose or guardianAngel.announcer then if guardianAngel.verbose or guardianAngel.announcer then trigger.action.outText("Guardian Angel has activated", 30) end
trigger.action.outText("Guardian Angel has activated", 30)
end
end end
function guardianAngel.doDeActivate() function guardianAngel.doDeActivate()
guardianAngel.active = false guardianAngel.active = false
if guardianAngel.verbose or guardianAngel.announcer then if guardianAngel.verbose or guardianAngel.announcer then trigger.action.outText("Guardian Angel NO LONGER ACTIVE", 30) end
trigger.action.outText("Guardian Angel NO LONGER ACTIVE", 30)
end
end end
function guardianAngel.flagUpdate() function guardianAngel.flagUpdate()
timer.scheduleFunction(guardianAngel.flagUpdate, {}, timer.getTime() + 1) -- once every second timer.scheduleFunction(guardianAngel.flagUpdate, {}, timer.getTime() + 1) -- once every second
-- check the flags for on/off
if guardianAngel.activate then if guardianAngel.activate then
if cfxZones.testZoneFlag(guardianAngel, guardianAngel.activate, "change","lastActivate") then if cfxZones.testZoneFlag(guardianAngel, guardianAngel.activate, "change","lastActivate") then
guardianAngel.doActivate() guardianAngel.doActivate()
end end
end end
if guardianAngel.deactivate then if guardianAngel.deactivate then
if cfxZones.testZoneFlag(guardianAngel, guardianAngel.deactivate, "change","lastDeActivate") then if cfxZones.testZoneFlag(guardianAngel, guardianAngel.deactivate, "change","lastDeActivate") then
guardianAngel.doDeActivate() guardianAngel.doDeActivate()
end end
end end
if #guardianAngel.sanctums > 0 then
-- scan all planes against sanctums
local filtered = {}
for idx, theZone in pairs(guardianAngel.sanctums) do
local f = theZone.floor
local c = theZone.ceiling
for coa=1, 2 do
for cat=0, 1 do
if theZone.coalition == 0 or theZone.coalition == coa then
local allGroups = coalition.getGroups(coa, cat)
for igp, aGroup in pairs(allGroups) do
local allUnits = aGroup:getUnits()
for iun, theUnit in pairs(allUnits) do
local p = theUnit:getPoint()
if f <= p.y and c >= p.y then
if theZone:isPointInsideZone(p) then
local name = theUnit:getName()
filtered[name] = theUnit
if theZone.verbose or guardianAngel.verbose then
if filtered[name] ~= guardianAngel.inSanctuary[name] then
if filtered[name] then
trigger.action.outText("+++gA: <" .. name .. "> now in sanctuary <" .. theZone.name .. ">", 30)
else
trigger.action.outText("+++gA: <" .. name .. "> left sanctum <" .. theZone.name .. ">", 30)
if guardianAngel.unitsToWatchOver[name] then
trigger.action.outText("(still protected, though)", 30)
end
end
end
end
end
end
end
end
end -- zone coa match
end -- cat loop
end -- coa loop
end
guardianAngel.inSanctuary = filtered
end
end end
function guardianAngel.collectPlayerUnits() function guardianAngel.collectPlayerUnits()
-- make sure we have all existing player units -- make sure we have all existing player units
-- at start of game -- at start of game
for i=1, 2 do for i=1, 2 do
-- currently only two factions in dcs -- currently only two factions in dcs
local factionUnits = coalition.getPlayers(i) local factionUnits = coalition.getPlayers(i)
for idx, theUnit in pairs(factionUnits) do for idx, theUnit in pairs(factionUnits) do
local mustProtect = false local mustProtect = false
if guardianAngel.autoAddPlayers then if guardianAngel.autoAddPlayer then mustProtect = true end
mustProtect = true
end
theZone = guardianAngel.getAngelicZoneForUnit(theUnit) theZone = guardianAngel.getAngelicZoneForUnit(theUnit)
if theZone then if theZone then
@ -815,11 +599,7 @@ function guardianAngel.collectPlayerUnits()
trigger.action.outText("+++gA: angelic zone " .. theZone.name .." contains player unit <" .. theUnit:getName() .. "> -- protect: (" .. dcsCommon.bool2YesNo(mustProtect) .. ")", 30) trigger.action.outText("+++gA: angelic zone " .. theZone.name .." contains player unit <" .. theUnit:getName() .. "> -- protect: (" .. dcsCommon.bool2YesNo(mustProtect) .. ")", 30)
end end
end end
if mustProtect then guardianAngel.addUnitToWatch(theUnit) end
if mustProtect then
guardianAngel.addUnitToWatch(theUnit)
end
end end
end end
end end
@ -844,10 +624,7 @@ function guardianAngel.collectAIUnits()
trigger.action.outText("+++gA: angelic zone <" .. theZone.name .."> contains AI unit <" .. theUnit:getName() .. ">, protect it: " .. dcsCommon.bool2YesNo(mustProtect) .. ".", 30) trigger.action.outText("+++gA: angelic zone <" .. theZone.name .."> contains AI unit <" .. theUnit:getName() .. ">, protect it: " .. dcsCommon.bool2YesNo(mustProtect) .. ".", 30)
end end
end end
if mustProtect then guardianAngel.addUnitToWatch(theUnit) end
if mustProtect then
guardianAngel.addUnitToWatch(theUnit)
end
end end
end end
end end
@ -863,10 +640,7 @@ function guardianAngel.readConfigZone()
if not theZone then if not theZone then
theZone = cfxZones.createSimpleZone("guardianAngelConfig") theZone = cfxZones.createSimpleZone("guardianAngelConfig")
end end
guardianAngel.verbose = theZone.verbose
guardianAngel.verbose = theZone:getBoolFromZoneProperty("verbose", false)
guardianAngel.autoAddPlayer = theZone:getBoolFromZoneProperty("autoAddPlayer", true) guardianAngel.autoAddPlayer = theZone:getBoolFromZoneProperty("autoAddPlayer", true)
guardianAngel.launchWarning = theZone:getBoolFromZoneProperty("launchWarning", true) guardianAngel.launchWarning = theZone:getBoolFromZoneProperty("launchWarning", true)
guardianAngel.intervention = theZone:getBoolFromZoneProperty("intervention", true) guardianAngel.intervention = theZone:getBoolFromZoneProperty("intervention", true)
@ -876,9 +650,7 @@ function guardianAngel.readConfigZone()
guardianAngel.fxDistance = theZone:getNumberFromZoneProperty( "fxDistance", 500) guardianAngel.fxDistance = theZone:getNumberFromZoneProperty( "fxDistance", 500)
guardianAngel.active = theZone:getBoolFromZoneProperty("active", true) guardianAngel.active = theZone:getBoolFromZoneProperty("active", true)
guardianAngel.msgTime = theZone:getNumberFromZoneProperty("msgTime", 30) guardianAngel.msgTime = theZone:getNumberFromZoneProperty("msgTime", 30)
if theZone:hasProperty("activate?") then if theZone:hasProperty("activate?") then
guardianAngel.activate = theZone:getStringFromZoneProperty("activate?", "*<none>") guardianAngel.activate = theZone:getStringFromZoneProperty("activate?", "*<none>")
guardianAngel.lastActivate = theZone:getFlagValue(guardianAngel.activate) guardianAngel.lastActivate = theZone:getFlagValue(guardianAngel.activate)
@ -895,28 +667,17 @@ function guardianAngel.readConfigZone()
guardianAngel.lastDeActivate = theZone:getFlagValue(guardianAngel.deactivate) guardianAngel.lastDeActivate = theZone:getFlagValue(guardianAngel.deactivate)
end end
if theZone:hasProperty("launchSound") then if theZone:hasProperty("launchSound") then guardianAngel.launchSound = theZone:getStringFromZoneProperty("launchSound", "nosound") end
guardianAngel.launchSound = theZone:getStringFromZoneProperty("launchSound", "nosound")
end
if theZone:hasProperty("interventionSound") then if theZone:hasProperty("interventionSound") then guardianAngel.interventionSound = theZone:getStringFromZoneProperty("interventionSound", "nosound") end
guardianAngel.interventionSound = theZone:getStringFromZoneProperty("interventionSound", "nosound")
end
guardianAngel.configZone = theZone guardianAngel.configZone = theZone
if guardianAngel.verbose then
trigger.action.outText("+++gA: processed config zone", 30)
end
end end
-- --
-- guardian zones -- guardian/sanctuary zones
-- --
function guardianAngel.processGuardianZone(theZone) function guardianAngel.processGuardianZone(theZone)
theZone.angelic = theZone:getBoolFromZoneProperty("guardian", true) theZone.angelic = true -- theZone:getBoolFromZoneProperty("guardian", true)
if theZone.verbose or guardianAngel.verbose then if theZone.verbose or guardianAngel.verbose then
trigger.action.outText("+++gA: processed 'guardian' zone <" .. theZone.name .. ">", 30) trigger.action.outText("+++gA: processed 'guardian' zone <" .. theZone.name .. ">", 30)
end end
@ -926,50 +687,48 @@ end
function guardianAngel.readGuardianZones() function guardianAngel.readGuardianZones()
local attrZones = cfxZones.getZonesWithAttributeNamed("guardian") local attrZones = cfxZones.getZonesWithAttributeNamed("guardian")
for k, aZone in pairs(attrZones) do for k, aZone in pairs(attrZones) do guardianAngel.processGuardianZone(aZone) end
guardianAngel.processGuardianZone(aZone)
end
end end
function guardianAngel.processSanctuaryZone(theZone)
theZone.floor = theZone:getNumberFromZoneProperty("floor", -1000)
theZone.ceiling = theZone:getNumberFromZoneProperty("ceiling", 100000)
theZone.coalition = theZone:getCoalitionFromZoneProperty("sanctuary", 0)
if theZone.verbose or guardianAngel.verbose then
trigger.action.outText("+++gA: processed 'sanctuary' zone <" .. theZone.name .. ">", 30)
end
-- add it to my sanctuaries
table.insert(guardianAngel.sanctums, theZone)
end
function guardianAngel.readSantuaryZones()
local attrZones = cfxZones.getZonesWithAttributeNamed("sanctuary")
for k, aZone in pairs(attrZones) do guardianAngel.processSanctuaryZone(aZone) end
end
-- --
-- start -- start
-- --
function guardianAngel.start() function guardianAngel.start()
-- lib check -- lib check
if not dcsCommon.libCheck("cfx Guardian Angel", if not dcsCommon.libCheck("cfx Guardian Angel", guardianAngel.requiredLibs) then return false end
guardianAngel.requiredLibs) then
return false
end
-- read config -- read config
guardianAngel.readConfigZone() guardianAngel.readConfigZone()
-- read guarded zones -- read guarded zones
guardianAngel.readGuardianZones() guardianAngel.readGuardianZones()
guardianAngel.readSantuaryZones()
-- install event monitor -- insert into evet loop
dcsCommon.addEventHandler(guardianAngel.somethingHappened, world.addEventHandler(guardianAngel)
guardianAngel.preProcessor,
guardianAngel.postProcessor)
-- collect all units that are already in the game at this point -- collect all units that are already in the game at this point
guardianAngel.collectPlayerUnits() guardianAngel.collectPlayerUnits(guardianAngel)
guardianAngel.collectAIUnits() guardianAngel.collectAIUnits()
-- start update for missiles
-- start update
guardianAngel.update() guardianAngel.update()
-- start flag/sanctuary checks
-- start flag check
guardianAngel.flagUpdate() guardianAngel.flagUpdate()
trigger.action.outText("Guardian Angel v" .. guardianAngel.version .. " running", 30) trigger.action.outText("Guardian Angel v" .. guardianAngel.version .. " running", 30)
return true return true
end end
function guardianAngel.testCB(reason, targetName, weaponName)
trigger.action.outText("gA - CB for ".. reason .. ": " .. targetName .. " w: " .. weaponName, guardianAngel.msgTime)
end
-- go go go -- go go go
if not guardianAngel.start() then if not guardianAngel.start() then
trigger.action.outText("Loading Guardian Angel failed.", 30) trigger.action.outText("Loading Guardian Angel failed.", 30)
@ -979,4 +738,7 @@ end
-- test callback -- test callback
--guardianAngel.addCallback(guardianAngel.testCB) --guardianAngel.addCallback(guardianAngel.testCB)
--guardianAngel.invokeCallbacks("A", "B", "C") --guardianAngel.invokeCallbacks("A", "B", "C")
--function guardianAngel.testCB(reason, targetName, weaponName)
-- trigger.action.outText("gA - CB for ".. reason .. ": " .. targetName .. " w: " .. weaponName, guardianAngel.msgTime)
--end

View File

@ -1,5 +1,5 @@
cfxHeloTroops = {} cfxHeloTroops = {}
cfxHeloTroops.version = "4.2.0" cfxHeloTroops.version = "4.2.1"
cfxHeloTroops.verbose = false cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false cfxHeloTroops.autoPickup = false
@ -19,6 +19,8 @@ cfxHeloTroops.requestRange = 500 -- meters
- code cleanup - code cleanup
4.2.0 - support for individual lase codes 4.2.0 - support for individual lase codes
- support for drivable - support for drivable
4.2.1 - increased verbosity
- also supports 'pickupRang" for reverse-compatibility with manual typo.
--]]-- --]]--
cfxHeloTroops.minTime = 3 -- seconds beween tandings cfxHeloTroops.minTime = 3 -- seconds beween tandings
@ -492,9 +494,11 @@ function cfxHeloTroops.addGroundMenu(conf)
end end
function cfxHeloTroops.filterTroopsByType(unitsToLoad) function cfxHeloTroops.filterTroopsByType(unitsToLoad)
if cfxHeloTroops.verbose then trigger.action.outText("+++heloT: enter filterTroops", 30) end
local filteredGroups = {} local filteredGroups = {}
for idx, aTeam in pairs(unitsToLoad) do for idx, aTeam in pairs(unitsToLoad) do
local group = aTeam.group local group = aTeam.group
if cfxHeloTroops.verbose then trigger.action.outText("+++heloT: testing group <" .. group:getName() .. ">", 30) end
local theTypes = dcsCommon.getGroupTypeString(group) local theTypes = dcsCommon.getGroupTypeString(group)
local aT = dcsCommon.splitString(theTypes, ",") local aT = dcsCommon.splitString(theTypes, ",")
@ -504,11 +508,19 @@ function cfxHeloTroops.filterTroopsByType(unitsToLoad)
if cfxHeloTroops.legalTroops then if cfxHeloTroops.legalTroops then
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then
pass = false pass = false
if cfxHeloTroops.verbose then
local gName = group:getName()
trigger.action.outText("+++heloT[legal]: type <" .. sT .. "> not in legalTroops, group <" .. gName .. "> discarded.", 30)
end
break break
end end
else else
if not dcsCommon.typeIsInfantry(sT) then if not dcsCommon.typeIsInfantry(sT) then
pass = false pass = false
if cfxHeloTroops.verbose then
local gName = group:getName()
trigger.action.outText("+++heloT[common]: type <" .. sT .. "> not in dcsC.infantry, group <" .. gName .. "> discarded.", 30)
end
break break
end end
end end
@ -519,13 +531,24 @@ function cfxHeloTroops.filterTroopsByType(unitsToLoad)
-- this one is managed by csarManager, -- this one is managed by csarManager,
-- don't load it for helo troops -- don't load it for helo troops
pass = false pass = false
if cfxHeloTroops.verbose then
local gName = group:getName()
trigger.action.outText("+++heloT[csar]: group <" .. gName .. "> filtered: is CSAR target.", 30)
end
end end
end end
if pass then if pass then
table.insert(filteredGroups, aTeam) table.insert(filteredGroups, aTeam)
if cfxHeloTroops.verbose then
local gName = group:getName()
trigger.action.outText("+++heloT[menu]: group <" .. gName .. "> added to available troops.", 30)
end
end end
end end
if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT[menu]: returning with <" .. #filteredGroups .. "> available groups", 30)
end
return filteredGroups return filteredGroups
end end
@ -981,6 +1004,9 @@ function cfxHeloTroops.readConfigZone()
cfxHeloTroops.autoDrop = theZone:getBoolFromZoneProperty("autoDrop", false) cfxHeloTroops.autoDrop = theZone:getBoolFromZoneProperty("autoDrop", false)
cfxHeloTroops.autoPickup = theZone:getBoolFromZoneProperty("autoPickup", false) cfxHeloTroops.autoPickup = theZone:getBoolFromZoneProperty("autoPickup", false)
cfxHeloTroops.pickupRange = theZone:getNumberFromZoneProperty("pickupRange", 100) cfxHeloTroops.pickupRange = theZone:getNumberFromZoneProperty("pickupRange", 100)
if theZone:hasProperty("pickupRang") then
cfxHeloTroops.pickupRange = theZone:getNumberFromZoneProperty("pickupRang", 100)
end
cfxHeloTroops.combatDropScore = theZone:getNumberFromZoneProperty( "combatDropScore", 200) cfxHeloTroops.combatDropScore = theZone:getNumberFromZoneProperty( "combatDropScore", 200)
cfxHeloTroops.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav") cfxHeloTroops.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav")

View File

@ -144,7 +144,7 @@ function jtacGrpUI.doCommandX(args)
local targetList = jtacGrpUI.collectJTACtargets(conf, true) local targetList = jtacGrpUI.collectJTACtargets(conf, true)
-- iterate the list -- iterate the list
if #targetList < 1 then if #targetList < 1 then
trigger.action.outTextForGroup(conf.id, "No targets are currently being lased", 30) trigger.action.outTextForGroup(conf.id, "No targets are currently being lased by ground forces", 30)
trigger.action.outSoundForGroup(conf.id, jtacGrpUI.jtacSound) trigger.action.outSoundForGroup(conf.id, jtacGrpUI.jtacSound)
return return
end end

188
modules/planeGuard.lua Normal file
View File

@ -0,0 +1,188 @@
planeGuard = {}
planeGuard.version = "1.0.0"
planeGuard.requiredLibs = {
"dcsCommon",
"cfxZones",
"cfxMX"
}
planeGuard.zones = {} -- all zones, indexed by name
--[[--
Version History
1.0.0 - Initial version
--]]--
-- read zone
function planeGuard.createPlaneGuardZone(theZone)
theZone.hType = theZone:getStringFromZoneProperty("planeGuard", "CH-53E")
theZone.alt = theZone:getNumberFromZoneProperty("alt", 40)
theZone.count = 1
-- immediately create the helo as plane guard
planeGuard.createGroupForZone(theZone)
end
function planeGuard.createGroupForZone(theZone)
local A = theZone:getPoint()
local theUnit, theGroup, idx = cfxMX.getClosestUnitToPoint(A, "ship")
if not theUnit then
trigger.action.outText("+++pGuard: can't find naval units for zone <" .. theUnit.name .. ">", 30)
return
end
if theZone.verbose or planeGuard.verbose then trigger.action.outText("+++pGuard: attaching guard to unit <" .. theUnit.name .. "> in group <" .. theGroup.name .. "> for zone <" .. theZone.name .. ">", 30) end
local h = theUnit.heading
if not h then h = 0 end
local ax = A.x
local ay = A.z -- !!!
local mainUnit = theGroup.units[1]
-- calc point relative to MAIN unit and project onto
-- rotated offsets
local pgx = ax - mainUnit.x
local pgy = ay - mainUnit.y
pgx, pgy = dcsCommon.rotatePointAroundOriginRad(pgx, pgy, -h)
local degrees = math.floor(h * 57.2958)
local theName = theZone.name .. "-" .. theZone.count
local sx = ax -- start coords
local sy = ay
theZone.count = theZone.count + 1
local cty = cfxMX.countryByName[theGroup.name]
local newGroupData = {
["tasks"] = {},
["radioSet"] = false,
["task"] = "Transport",
["route"] = {
["points"] = {
[1] = {
["alt"] = 40,
["action"] = "Turning Point",
["alt_type"] = "BARO",
["properties"] = {["addopt"] = {},},
["speed"] = 7,
["task"] = {
["id"] = "ComboTask",
["params"] = {
["tasks"] = {
[1] = {
["enabled"] = true,
["auto"] = false,
["id"] = "WrappedAction",
["number"] = 1,
["params"] = {
["action"] = {
["id"] = "SetUnlimitedFuel",
["params"] = { ["value"] = true, }, -- end of ["params"]
}, -- end of ["action"]
}, -- end of ["params"]
}, -- end of [1]
}, -- end of ["tasks"]
}, -- end of ["params"]
}, -- end of ["task"]
["type"] = "Turning Point",
["ETA"] = 0,
["ETA_locked"] = true,
["y"] = sy + 100, -- start here
["x"] = sx + 100, -- start here
["speed_locked"] = true,
}, -- end of [1]
[2] = {
["alt"] = 40,
["action"] = "Turning Point",
["alt_type"] = "BARO",
["properties"] = {["addopt"] = {},},
["speed"] = 7,
["task"] = {
["id"] = "ComboTask",
["params"] = {
["tasks"] = {
[1] = {
["number"] = 1,
["auto"] = false,
["id"] = "ControlledTask",
["enabled"] = true,
["params"] = {
["task"] = {
["id"] = "Follow",
["params"] = {
["lastWptIndexFlagChangedManually"] = true,
["x"] = sx,
["groupId"] = theGroup.groupId, --1, -- group to follow
["lastWptIndex"] = 4,
["lastWptIndexFlag"] = false,
["y"] = sy,
["pos"] = {
["y"] = theZone.alt, -- alt
["x"] = pgx, --(up/down)
["z"] = pgy, --(left/right)
}, -- end of ["pos"]
}, -- end of ["params"]
}, -- end of ["task"]
["stopCondition"] = {["userFlag"] = "999",}, -- end of ["stopCondition"]
}, -- end of ["params"]
}, -- end of [1]
}, -- end of ["tasks"]
}, -- end of ["params"]
}, -- end of ["task"]
["type"] = "Turning Point",
["y"] = sy + 100,
["x"] = sx + 100,
}, -- end of [2]
}, -- end of ["points"]
}, -- end of ["route"]
["hidden"] = false,
["units"] = {
[1] = {
["alt"] = 40,
["alt_type"] = "BARO",
["skill"] = "High",
["speed"] = 7,
["type"] = theZone.hType,
["y"] = sy + 100,
["x"] = sx + 100,
["name"] = theName .. "-1",
["heading"] = 0,
}, -- end of [1]
}, -- end of ["units"]
["y"] = sy + 100,
["x"] = sx + 100,
["name"] = theName,
} -- end of theGroup
-- allocate the new group. Always helo
local pgGroup = coalition.addGroup(cty, 1, newGroupData)
end
-- config
function planeGuard.readConfigZone()
local theZone = cfxZones.getZoneByName("planeGuardConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("planeGuardConfig")
end
planeGuard.verbose = theZone.verbose
end
-- go go go
function planeGuard.start()
-- lib check
if not dcsCommon.libCheck then
trigger.action.outText("planeGuard requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("planeGuard", planeGuard.requiredLibs) then
return false
end
-- read config
planeGuard.readConfigZone()
-- process "planeGuard" Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("planeGuard")
for k, aZone in pairs(attrZones) do
planeGuard.createPlaneGuardZone(aZone)
planeGuard.zones[aZone.name] = aZone
end
trigger.action.outText("planeGuard v" .. planeGuard.version .. " started.", 30)
return true
end
-- let's go!
if not planeGuard.start() then
trigger.action.outText("planeGuard aborted: error on start", 30)
planeGuard = nil
end

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +1,20 @@
cfxPlayerScoreUI = {} cfxPlayerScoreUI = {}
cfxPlayerScoreUI.version = "3.0.1" cfxPlayerScoreUI.version = "3.1.0"
cfxPlayerScoreUI.verbose = false
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
- 1.0.2 - initial version
- 1.0.3 - module check
- 2.0.0 - removed cfxPlayer dependency, handles own commands
- 2.0.1 - late start capability
- 2.1.0 - soundfile cleanup
- score summary for side
- allowAll
- 2.1.1 - minor cleanup
- 3.0.0 - compatible with dynamic groups/units in DCS 2.9.6 - 3.0.0 - compatible with dynamic groups/units in DCS 2.9.6
- 3.0.1 - more hardening - 3.0.1 - more hardening
- 3.1.0 - CA support
- playerScoreUI correct ion for some methods
- config zone support, attributes allowAll, soundFile, ranked
- attachTo: support
--]]-- --]]--
cfxPlayerScoreUI.requiredLibs = { cfxPlayerScoreUI.requiredLibs = {
"dcsCommon", -- this is doing score keeping "dcsCommon", -- this is doing score keeping
"cfxZones", -- zones for config "cfxZones", -- zones for config
"cfxPlayerScore", "cfxPlayerScore",
} }
cfxPlayerScoreUI.soundFile = "Quest Snare 3.wav"
cfxPlayerScoreUI.rootCommands = {} -- by unit's GROUP name, for player aircraft. stores command roots cfxPlayerScoreUI.rootCommands = {} -- by unit's GROUP name, for player aircraft. stores command roots
cfxPlayerScoreUI.allowAll = true
cfxPlayerScoreUI.ranked = true
-- redirect: avoid the debug environ of missionCommands -- redirect: avoid the debug environ of missionCommands
function cfxPlayerScoreUI.redirectCommandX(args) function cfxPlayerScoreUI.redirectCommandX(args)
@ -38,20 +29,16 @@ function cfxPlayerScoreUI.doCommandX(args)
if not theGroup then return end -- should not happen if not theGroup then return end -- should not happen
local gid = theGroup:getID() local gid = theGroup:getID()
local coa = theGroup:getCoalition() local coa = theGroup:getCoalition()
if not cfxPlayerScore.scoreTextForPlayerNamed then if not cfxPlayerScore.scoreTextForPlayerNamed then
trigger.action.outText("***pSGUI: CANNOT FIND PlayerScore MODULE", 30) trigger.action.outText("***pSGUI: CANNOT FIND PlayerScore MODULE", 30)
return return
end end
local desc = "" local desc = ""
if what == "score" then if what == "score" then desc = cfxPlayerScore.scoreTextForPlayerNamed(playerName)
desc = cfxPlayerScore.scoreTextForPlayerNamed(playerName) elseif what == "allMySide" then desc = cfxPlayerScore.scoreSummaryForPlayersOfCoalition(coa)
elseif what == "allMySide" then
desc = cfxPlayerScore.scoreSummaryForPlayersOfCoalition(coa)
elseif what == "all" then elseif what == "all" then
desc = "Score Table For All Players:\n" .. cfxPlayerScore.scoreTextForAllPlayers(cfxPlayerScoreUI.ranked) desc = "Score Table For All Players:\n" .. cfxPlayerScore.scoreTextForAllPlayers(cfxPlayerScoreUI.ranked)
else else desc = "PlayerScore UI: unknown command <" .. what .. ">"
desc = "PlayerScore UI: unknown command <" .. what .. ">"
end end
trigger.action.outTextForGroup(gid, desc, 30) trigger.action.outTextForGroup(gid, desc, 30)
trigger.action.outSoundForGroup(gid, cfxPlayerScoreUI.soundFile) trigger.action.outSoundForGroup(gid, cfxPlayerScoreUI.soundFile)
@ -61,69 +48,83 @@ end
-- event handling: we are only interested in birth events -- event handling: we are only interested in birth events
-- for player aircraft -- for player aircraft
-- --
function cfxPlayerScore.processPlayerUnit(theUnit) function cfxPlayerScoreUI.processPlayerUnit(theUnit)
if not theUnit.getPlayerName then return end -- no player name, bye! if not theUnit.getPlayerName then return end -- no player name, bye!
local playerName = theUnit:getPlayerName() local playerName = theUnit:getPlayerName()
if not playerName then return end if not playerName then return end
-- now we know it's a player unit. get group name
-- so now we know it's a player plane. get group name
local theGroup = theUnit:getGroup() local theGroup = theUnit:getGroup()
local groupName = theGroup:getName() local groupName = theGroup:getName()
local gid = theGroup:getID() local gid = theGroup:getID()
-- handle main menu
local mainMenu = nil
if cfxPlayerScoreUI.mainMenu then
mainMenu = radioMenu.getMainMenuFor(cfxPlayerScoreUI.mainMenu)
end
-- see if this group already has a score command -- see if this group already has a score command
if cfxPlayerScoreUI.rootCommands[groupName] then if cfxPlayerScoreUI.rootCommands[groupName] then
-- need re-init to store new pilot name -- need re-init to store new pilot name
if cfxPlayerScoreUI.verbose then if cfxPlayerScoreUI.verbose then trigger.action.outText("++pSGui: group <" .. groupName .. "> already has score menu, removing.", 30) end
trigger.action.outText("++pSGui: group <" .. groupName .. "> already has score menu, removing.", 30)
end
missionCommands.removeItemForGroup(gid, cfxPlayerScoreUI.rootCommands[groupName]) missionCommands.removeItemForGroup(gid, cfxPlayerScoreUI.rootCommands[groupName])
cfxPlayerScoreUI.rootCommands[groupName] = nil cfxPlayerScoreUI.rootCommands[groupName] = nil
end end
-- we install a group menu item for scores.
-- we need to install a group menu item for scores.
-- will persist through death
local commandTxt = "Show Score / Kills" local commandTxt = "Show Score / Kills"
local theMenu = missionCommands.addSubMenuForGroup(gid, "Show Score", nil) local theMenu = missionCommands.addSubMenuForGroup(gid, cfxPlayerScoreUI.menuName, mainMenu)
local theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "score"}) local theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "score"})
commandTxt = "Show my Side Score / Kills" commandTxt = "Show my Side Score / Kills"
theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "allMySide"}) theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "allMySide"})
if cfxPlayerScoreUI.allowAll then if cfxPlayerScoreUI.allowAll then
commandTxt = "Show All Player Scores" commandTxt = "Show All Player Scores"
theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "all"}) theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "all"})
end end
cfxPlayerScoreUI.rootCommands[groupName] = theMenu cfxPlayerScoreUI.rootCommands[groupName] = theMenu
if cfxPlayerScoreUI.verbose then if cfxPlayerScoreUI.verbose then
trigger.action.outText("++pSGui: installed player score menu for group <" .. groupName .. ">", 30) trigger.action.outText("++pSGui: installed player score menu for group <" .. groupName .. ">", 30)
end end
end end
function cfxPlayerScoreUI:onEvent(event) function cfxPlayerScoreUI:onEvent(event)
if event.id ~= 15 then return end -- only birth if event.id ~= 20 and -- S_EVENT_PLAYER_ENTER_UNIT -- CA support
event.id ~= 15 then return end -- birth
if not event.initiator then return end -- no initiator, no joy if not event.initiator then return end -- no initiator, no joy
local theUnit = event.initiator local theUnit = event.initiator
cfxPlayerScore.processPlayerUnit(theUnit) cfxPlayerScoreUI.processPlayerUnit(theUnit)
end end
function cfxPlayerScoreUI.readConfig()
cfxPlayerScoreUI.name = "playerScoreUIConfig"
local theZone = cfxZones.getZoneByName("playerScoreUIConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("playerScoreUIConfig")
end
if theZone:hasProperty("attachTo:") then
local attachTo = theZone:getStringFromZoneProperty("attachTo:", "<none>")
if radioMenu then -- requires optional radio menu to have loaded
local mainMenu = radioMenu.mainMenus[attachTo]
if mainMenu then cfxPlayerScoreUI.mainMenu = mainMenu
else trigger.action.outText("+++PlayerScoreUI: cannot find super menu <" .. attachTo .. ">", 30) end
else
trigger.action.outText("+++PlayerScoreUI: REQUIRES radioMenu to run before PlayerScoreUI. 'AttachTo:' ignored.", 30)
end
end
cfxPlayerScoreUI.menuName = theZone:getStringFromZoneProperty("menuName", "Show Score")
cfxPlayerScoreUI.soundFile = theZone:getStringFromZoneProperty("SoundFile", "Quest Snare 3.wav")
cfxPlayerScoreUI.allowAll = theZone:getBoolFromZoneProperty("allowAll", true)
cfxPlayerScoreUI.ranked = theZone:getBoolFromZoneProperty("ranked", true)
cfxPlayerScoreUI.verbose = theZone.verbose
end
-- --
-- Start -- Start
-- --
function cfxPlayerScoreUI.start() function cfxPlayerScoreUI.start()
if not dcsCommon.libCheck("cfx Player Score UI", if not dcsCommon.libCheck("cfx Player Score UI", cfxPlayerScoreUI.requiredLibs) then return false end
cfxPlayerScoreUI.requiredLibs) -- install event handler for new player planes and CA
then return false end
-- install the event handler for new player planes
world.addEventHandler(cfxPlayerScoreUI) world.addEventHandler(cfxPlayerScoreUI)
-- process all existing players (late start) cfxPlayerScoreUI.readConfig()
dcsCommon.iteratePlayers(cfxPlayerScore.processPlayerUnit)
trigger.action.outText("cf/x PlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30) trigger.action.outText("cf/x PlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30)
return true return true
end end
-- --
-- GO GO GO -- GO GO GO
-- --

View File

@ -1,5 +1,5 @@
pulseFlags = {} pulseFlags = {}
pulseFlags.version = "2.0.1" pulseFlags.version = "2.0.2"
pulseFlags.verbose = false pulseFlags.verbose = false
pulseFlags.requiredLibs = { pulseFlags.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -14,7 +14,7 @@ pulseFlags.requiredLibs = {
- 2.0.0 dmlZones / OOP - 2.0.0 dmlZones / OOP
using method on all outputs using method on all outputs
- 2.0.1 activateZoneFlag now works correctly - 2.0.1 activateZoneFlag now works correctly
- 2.0.2 fixed scheduledTime bug while persisting
--]]-- --]]--
pulseFlags.pulses = {} pulseFlags.pulses = {}
@ -264,7 +264,11 @@ function pulseFlags.saveData()
pulseData.pulsePaused = thePulse.pulsePaused pulseData.pulsePaused = thePulse.pulsePaused
pulseData.pulsesLeft = thePulse.pulsesLeft pulseData.pulsesLeft = thePulse.pulsesLeft
pulseData.pulsing = thePulse.pulsing pulseData.pulsing = thePulse.pulsing
pulseData.scheduledTime = thePulse.scheduledTime - now if thePulse.scheduledTime then
pulseData.scheduledTime = thePulse.scheduledTime - now
else
pulseData.scheduledTime = -1
end
pulseData.hasPulsed = thePulse.hasPulsed pulseData.hasPulsed = thePulse.hasPulsed
allPulses[theName] = pulseData allPulses[theName] = pulseData

View File

@ -1,5 +1,5 @@
radioMenu = {} radioMenu = {}
radioMenu.version = "4.0.2" radioMenu.version = "4.1.0"
radioMenu.verbose = false radioMenu.verbose = false
radioMenu.ups = 1 radioMenu.ups = 1
radioMenu.requiredLibs = { radioMenu.requiredLibs = {

View File

@ -1,5 +1,5 @@
cfxSmokeZone = {} cfxSmokeZone = {}
cfxSmokeZone.version = "2.0.0" cfxSmokeZone.version = "2.0.1"
cfxSmokeZone.requiredLibs = { cfxSmokeZone.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course

View File

@ -1,5 +1,5 @@
cfxSpawnZones = {} cfxSpawnZones = {}
cfxSpawnZones.version = "3.0.0" cfxSpawnZones.version = "3.0.1"
cfxSpawnZones.requiredLibs = { cfxSpawnZones.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
-- pretty stupid to check for this since we -- pretty stupid to check for this since we
@ -19,11 +19,13 @@ cfxSpawnZones.spawnedGroups = {}
-- --
-- Zones that conform with this requirements spawn toops automatically -- Zones that conform with this requirements spawn toops automatically
-- *** DOES !NOT! EXTEND ZONES *** LINKED OWNER via masterOwner *** -- *** DOES !NOT! EXTEND ZONES *** LINKED OWNER via masterOwner ***
-- theSpawner.zone links back to dml zone that created spawner
-- --
--[[-- --[[--
-- version history -- version history
3.0.0 - supports zone-individual laser code for "lase" orders 3.0.0 - supports zone-individual laser code for "lase" orders
- drivable attribute passed to groundTroops - drivable attribute passed to groundTroops
3.0.1 - increased vorbosity
--]]-- --]]--
@ -214,7 +216,7 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
local delta = dcsCommon.distFlat(aPoint, cfxZones.getPoint(aZone)) local delta = dcsCommon.distFlat(aPoint, cfxZones.getPoint(aZone))
if delta>aRange then if delta>aRange then
hasMatch = false hasMatch = false
-- reasons = reasons .. "[distance " .. math.floor(delta) .. "] reasons = reasons .. "[distance " .. math.floor(delta) .. "] "
end end
if aSide ~= 0 then if aSide ~= 0 then
-- check if side is correct for owned zone -- check if side is correct for owned zone
@ -222,7 +224,7 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
-- failed ownership test. owner of master -- failed ownership test. owner of master
-- is not my own zone -- is not my own zone
hasMatch = false hasMatch = false
-- reasons = reasons .. "[sawnOwnership] " reasons = reasons .. "[spawnOwnership] "
end end
end end
@ -230,7 +232,7 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
-- only return spawners with this side -- only return spawners with this side
-- note: this will NOT work with neutral players -- note: this will NOT work with neutral players
hasMatch = false hasMatch = false
-- reasons = reasons .. "[rawOwner] " reasons = reasons .. "[rawOwner] "
end end
if not aSpawner.requestable then if not aSpawner.requestable then
@ -239,9 +241,13 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
if hasMatch then if hasMatch then
table.insert(theSpawners, aSpawner) table.insert(theSpawners, aSpawner)
-- trigger.action.outText("+++Spwn: ELIGIBLE spawner <" .. aSpawner.name .. ">", 30) if aSpawner.zone.verbose then
-- else trigger.action.outText("+++Spwn: ELIGIBLE spawner <" .. aSpawner.name .. ">", 30)
-- trigger.action.outText("+++Spwn: spawner <" .. aSpawner.name .. "> not eligible because " .. reasons, 30) end
else
if aSpawner.zone.verbose then
trigger.action.outText("+++Spwn: spawner <" .. aSpawner.name .. "> not eligible because " .. reasons, 30)
end
end end
end end

View File

@ -1,5 +1,5 @@
unitZone={} unitZone={}
unitZone.version = "2.0.0" unitZone.version = "2.0.1"
unitZone.verbose = false unitZone.verbose = false
unitZone.ups = 1 unitZone.ups = 1
unitZone.requiredLibs = { unitZone.requiredLibs = {
@ -8,16 +8,6 @@ unitZone.requiredLibs = {
} }
--[[-- --[[--
Version History Version History
1.0.0 - Initial Version
1.1.0 - DML flag integration
- method/uzMethod
1.2.0 - uzOn?, uzOff?, triggerMethod
1.2.1 - uzDirect
1.2.2 - uzDirectInv
1.2.3 - better guards for enterZone!, exitZone!, changeZone!
- better guards for uzOn? and uzOff?
1.2.4 - more verbosity on uzDirect
1.2.5 - reading config improvement
2.0.0 - matchAll option (internal, automatic look for "*" in names) 2.0.0 - matchAll option (internal, automatic look for "*" in names)
- lookFor defaults to "*" - lookFor defaults to "*"
- OOP dmlZones - OOP dmlZones
@ -27,6 +17,7 @@ unitZone.requiredLibs = {
- unitZone now used to define the coalition, coalition DEPRECATED - unitZone now used to define the coalition, coalition DEPRECATED
- filter synonym - filter synonym
- direct#, directInv# synonyms - direct#, directInv# synonyms
2.0.1 - code hardening
--]]-- --]]--
unitZone.unitZones = {} unitZone.unitZones = {}
@ -183,14 +174,10 @@ function unitZone.createUnitZone(theZone)
if unitZone.verbose or theZone.verbose then if unitZone.verbose or theZone.verbose then
trigger.action.outText("+++uZne: processsed unit zone <" .. theZone.name .. "> with status = (" .. dcsCommon.bool2Text(theZone.lastStatus) .. ")", 30) trigger.action.outText("+++uZne: processsed unit zone <" .. theZone.name .. "> with status = (" .. dcsCommon.bool2Text(theZone.lastStatus) .. ")", 30)
end end
end end
-- --
-- process zone -- process zone
-- --
function unitZone.collectGroups(theZone) function unitZone.collectGroups(theZone)
local collector = {} -- players: units, groups: groups local collector = {} -- players: units, groups: groups
if theZone.matching == "player" then if theZone.matching == "player" then
@ -231,7 +218,6 @@ end
function unitZone.checkZoneStatus(theZone) function unitZone.checkZoneStatus(theZone)
-- returns true (at least one unit found in zone) -- returns true (at least one unit found in zone)
-- or false (no unit found in zone) -- or false (no unit found in zone)
-- collect all groups to inspect -- collect all groups to inspect
local theGroups = unitZone.collectGroups(theZone) local theGroups = unitZone.collectGroups(theZone)
local lookFor = theZone.lookFor local lookFor = theZone.lookFor
@ -241,18 +227,20 @@ function unitZone.checkZoneStatus(theZone)
-- we check the names for players only -- we check the names for players only
-- collector holds units for players, not groups -- collector holds units for players, not groups
for idx, pUnit in pairs(theGroups) do for idx, pUnit in pairs(theGroups) do
local puName = pUnit:getName() if Unit.isExist(pUnit) then
local hasMatch = theZone.matchAll local puName = pUnit:getName()
if not hasMatch then local hasMatch = theZone.matchAll
if theZone.lookForBeginsWith then if not hasMatch then
hasMatch = dcsCommon.stringStartsWith(puName, lookFor) if theZone.lookForBeginsWith then
else hasMatch = dcsCommon.stringStartsWith(puName, lookFor)
hasMatch = puName == lookFor else
hasMatch = puName == lookFor
end
end end
end if hasMatch then
if hasMatch then if cfxZones.unitInZone(pUnit, theZone) then
if cfxZones.unitInZone(pUnit, theZone) then return true
return true end
end end
end end
end end
@ -260,21 +248,23 @@ function unitZone.checkZoneStatus(theZone)
else else
-- we perform group check. -- we perform group check.
for idx, aGroup in pairs(theGroups) do for idx, aGroup in pairs(theGroups) do
local gName=aGroup:getName() if Group.isExist(aGroup) then
local hasMatch = theZone.matchAll local gName=aGroup:getName()
if not hasMatch then local hasMatch = theZone.matchAll
if theZone.lookForBeginsWith then if not hasMatch then
hasMatch = dcsCommon.stringStartsWith(gName, lookFor) if theZone.lookForBeginsWith then
else hasMatch = dcsCommon.stringStartsWith(gName, lookFor)
hasMatch = gName == lookFor else
hasMatch = gName == lookFor
end
end end
end if hasMatch and aGroup:isExist() then
if hasMatch and aGroup:isExist() then -- check all living units in zone
-- check all living units in zone local gUnits = aGroup:getUnits()
local gUnits = aGroup:getUnits() for idy, aUnit in pairs (gUnits) do
for idy, aUnit in pairs (gUnits) do if cfxZones.unitInZone(aUnit, theZone) then
if cfxZones.unitInZone(aUnit, theZone) then return true
return true end
end end
end end
end end
@ -282,7 +272,6 @@ function unitZone.checkZoneStatus(theZone)
end end
return false return false
end end
-- --
-- update -- update
-- --
@ -365,7 +354,6 @@ function unitZone.update()
end end
end end
end end
-- --
-- Config & Start -- Config & Start
-- --
@ -375,11 +363,8 @@ function unitZone.readConfigZone()
if not theZone then if not theZone then
theZone = cfxZones.createSimpleZone("unitZoneConfig") theZone = cfxZones.createSimpleZone("unitZoneConfig")
end end
unitZone.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) unitZone.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
unitZone.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1) unitZone.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1)
if unitZone.verbose then if unitZone.verbose then
trigger.action.outText("+++uZne: read config", 30) trigger.action.outText("+++uZne: read config", 30)
end end
@ -394,20 +379,16 @@ function unitZone.start()
if not dcsCommon.libCheck("cfx Unit Zone", unitZone.requiredLibs) then if not dcsCommon.libCheck("cfx Unit Zone", unitZone.requiredLibs) then
return false return false
end end
-- read config -- read config
unitZone.readConfigZone() unitZone.readConfigZone()
-- process cloner Zones -- process cloner Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("unitZone") local attrZones = cfxZones.getZonesWithAttributeNamed("unitZone")
for k, aZone in pairs(attrZones) do for k, aZone in pairs(attrZones) do
unitZone.createUnitZone(aZone) -- process attributes unitZone.createUnitZone(aZone) -- process attributes
unitZone.addUnitZone(aZone) -- add to list unitZone.addUnitZone(aZone) -- add to list
end end
-- start update -- start update
unitZone.update() unitZone.update()
trigger.action.outText("cfx Unit Zone v" .. unitZone.version .. " started.", 30) trigger.action.outText("cfx Unit Zone v" .. unitZone.version .. " started.", 30)
return true return true
end end
@ -418,6 +399,5 @@ if not unitZone.start() then
unitZone = nil unitZone = nil
end end
--ToDo: add 'neutral' support and add 'both' option --ToDo: add 'neutral' support and add 'both' option
--ToDo: add API --ToDo: add API

Binary file not shown.

Binary file not shown.