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.version = "3.0.0"
cfxMX.version = "3.0.1"
cfxMX.verbose = false
--[[--
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
- pre-populate spawnedUnits coa, cat from MX
- spawnedUnitGroupNameByName
3.0.1 - new getClosestUnitToPoint()
--]]--
@ -33,7 +34,7 @@ cfxMX.groupNamesByID = {}
cfxMX.groupIDbyName = {}
cfxMX.unitIDbyName = {}
cfxMX.groupCatByName = {}
cfxMX.groupDataByName = {}
cfxMX.groupDataByName = {} -- includes static groups!
cfxMX.groupTypeByName = {} -- category of group: "helicopter", "plane", "ship"...
cfxMX.groupCoalitionByName = {}
cfxMX.groupHotByName = {}
@ -413,6 +414,34 @@ function cfxMX.allGroupsInZoneByData(theZone, cat) -- returns groups indexed by
return theGroupsInZone, count
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)
if not theUnit then return false end
if not theUnit.getName then return false end

View File

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

View File

@ -1,84 +1,37 @@
guardianAngel = {}
guardianAngel.version = "3.0.6"
guardianAngel.ups = 10
guardianAngel.version = "4.0.0"
guardianAngel.ups = 10 -- hard-coded!! missile track
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 = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
}
-- Guardian Angel DML script (c) 2021-2025 by Charistian Franz
--[[--
Version History
1.0.0 - Initial version
2.0.0 - autoAddPlayer
- verbose
- lib check
- config zone
- sneaky detection logic
- god intervention 100m
- detect re-acquisition
- addUnitToWatch supports string names
- detect non-miss
- announcer
- intervene optional
- launch warning optional
2.0.1 - warnings go to group
- 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
4.0.0 - code cleanup
- DCS bug hardening
- guardianAngel.autoAddPlayer implemented
- removed preProcessor
- removed postproc
- removed isInterestingEvent
- wired directly to onEvent
- removed event id 20 (player enter)
- mild performance tuning
- new sanctuary zones
- sancturay zones have floor and ceiling
- once per second sanctuary calc
- expanded getWatchedUnitByName to include inSanctuary
- sanctuary zones use coalition
--]]--
guardianAngel.minMissileDist = 50 -- m. below this distance the missile is killed by god, not the angel :)
guardianAngel.myEvents = {1, 15, 20, 21, 23, 2} -- 1 - shot, 15 - birth, 20 - enter unit, 21 - player leave unit, 23 - start shooting
-- added 2 (hit) event to see if angel was defeated
guardianAngel.active = true -- can be turned on / off
guardianAngel.angelicZones = {} -- angels be active here
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.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.missilesAndTargets = {} -- permanent log which missile was aimed at whom
guardianAngel.callBacks = {} -- callbacks
@ -96,45 +49,35 @@ function guardianAngel.invokeCallbacks(reason, targetName, weaponName)
theCB(reason, targetName, weaponName)
end
end
--
-- units to watch
-- units to watch (permanent protection list)
--
function guardianAngel.addUnitToWatch(aUnit)
if type(aUnit) == "string" then
aUnit = Unit.getByName(aUnit)
end
if not aUnit then return end
local unitName = aUnit:getName()
local isNew = guardianAngel.unitsToWatchOver[unitName] == nil
guardianAngel.unitsToWatchOver[unitName] = aUnit
if guardianAngel.verbose then
if isNew then
trigger.action.outText("+++gA: now watching unit " .. unitName, 30)
else
trigger.action.outText("+++gA: updating unit " .. unitName, 30)
if isNew then trigger.action.outText("+++gA: now guarding unit " .. unitName, 30)
else trigger.action.outText("+++gA: updating unit " .. unitName, 30)
end
end
end
function guardianAngel.removeUnitToWatch(aUnit)
if type(aUnit) == "string" then
aUnit = Unit.getByName(aUnit)
end
if not aUnit then return end
local unitName = aUnit:getName()
if not unitName then return end
guardianAngel.unitsToWatchOver[unitName] = nil
if guardianAngel.verbose then
trigger.action.outText("+++gA: no longer watching " .. aUnit:getName(), 30)
end
if guardianAngel.verbose then trigger.action.outText("+++gA: no longer watching " .. aUnit:getName(), 30) end
end
function guardianAngel.getWatchedUnitByName(aName)
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
--
-- watch q items
--
@ -145,28 +88,22 @@ function guardianAngel.createQItem(theWeapon, theTarget, threat, launcher)
if not threat then threat = false end
-- if an item is not a 'threat' it means that we merely
-- watch it for re-targeting purposes
local theItem = {}
local oName = tostring(theWeapon:getName())
if not oName or #oName < 1 then oName = dcsCommon.numberUUID() end
local wName = ""
if theWeapon.getDisplayName then
wName = theWeapon:getDisplayName() -- does this even exist any more?
elseif theWeapon.getTypeName then
wName = theWeapon:getTypeName()
else
wName = "<Generic>"
end
elseif theWeapon.getTypeName then wName = theWeapon:getTypeName()
else wName = "<Generic>" end
wName = wName .. "-" .. oName
local launcherName = launcher:getTypeName() .. " " .. launcher:getName()
theItem.theWeapon = theWeapon -- weapon that we are tracking
theItem.weaponName = wName -- theWeapon:getName()
-- usually weapons have no 'name' except an ID, so let's get the
-- type or display name. Weapons often have no display name.
if guardianAngel.verbose then
trigger.action.outText("gA: tracking missile <" .. wName .. "> launched by <" .. launcherName .. ">", guardianAngel.msgTime)
end
-- usually weapons have no 'name' except an ID, so let's get
-- type/display name. Weapons often have no display name.
if guardianAngel.verbose then trigger.action.outText("gA: tracking missile <" .. wName .. "> launched by <" .. launcherName .. ">", guardianAngel.msgTime) end
theItem.theTarget = theTarget
if theTarget.getGroup then -- some targets may not have a group
theItem.tGroup = theTarget:getGroup()
@ -202,16 +139,12 @@ function guardianAngel.retargetItem(theItem, theTarget, threat)
if not threat then threat = false end
theItem.timeStamp = timer.getTime()
theItem.threat = threat
theItem.theTarget = theTarget
if not theTarget.getGroup then
local theCat = theTarget:getCategory()
if theCat ~= 2 then
-- not a weapon / flare
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
else
theItem.tGroup = theTarget:getGroup()
@ -219,16 +152,13 @@ function guardianAngel.retargetItem(theItem, theTarget, threat)
end
theItem.targetName = theTarget:getName()
theItem.lastDistance = math.huge
--theItem.lostTrack = false
theItem.missed = false
theItem.lastDesc = "(retarget)"
end
function guardianAngel.getQItemForWeaponNamed(theName)
for idx, theItem in pairs (guardianAngel.missilesInTheAir) do
if theItem.weaponName == theName then
return theItem
end
if theItem.weaponName == theName then return theItem end
end
return nil
end
@ -239,11 +169,9 @@ function guardianAngel.calcSafeExplosionPoint(wpn, pln, dist)
local v = dcsCommon.vNorm(dirToWpn) -- |v| = 1
local v = dcsCommon.vMultScalar(v, dist) -- |v| = dist
local newPoint = dcsCommon.vAdd(pln, v)
--trigger.action.outText("+++ gA: safe dist is ".. dist, 30)
return newPoint
end
function guardianAngel.monitorItem(theItem)
local w = theItem.theWeapon
local ID = theItem.tID
@ -258,31 +186,14 @@ function guardianAngel.monitorItem(theItem)
local t = theItem.theTarget
local currentTarget = w:getTarget()
-- Re-target check. did missile pick a new target?
-- this can happen with any missile, even threat missiles,
-- so do this always!
local ctName = nil
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
local ctName = "***guardianAngel.not.set"
if currentTarget and Object.isExist(currentTarget) then ctName = currentTarget:getName() end
if ctName ~= theItem.targetName then
-- see if it's a threat to us now
local watchedUnit = guardianAngel.getWatchedUnitByName(ctName)
-- update the db who's seeking who
guardianAngel.missilesAndTargets[theItem.weaponName] = ctName
-- should now update theItem to new target info
isThreat = false
if guardianAngel.getWatchedUnitByName(ctName) then
@ -290,7 +201,6 @@ function guardianAngel.monitorItem(theItem)
if guardianAngel.verbose then
trigger.action.outText("+++gA: <" .. theItem.weaponName .. "> now targeting protected <" .. ctName .. ">!", 30)
end
if isThreat and guardianAngel.announcer and guardianAngel.active then
local desc = "Missile, missile, missile - now heading for " .. ctName .. "!"
if guardianAngel.private and ID then
@ -307,31 +217,21 @@ function guardianAngel.monitorItem(theItem)
end
end
end
end
end
guardianAngel.retargetItem(theItem, currentTarget, isThreat)
t = currentTarget
else
-- not ctName, or name as before.
-- go on.
end
-- we only progress here is the missile is a threat.
-- if not, we keep it and check next time if it has
-- retargeted a protegee
if not theItem.threat then return true end
-- local oldWPos = theItem.wP
local A = w:getPoint() -- A is new point of weapon
-- theItem.wp = A -- update new position, old is in oldWPos
if not theItem.threat then return true end
local A = w:getPoint() -- A is new point of weapon
-- new code: safety check with ALL protected wings
-- local bubbleThreat = guardianAngel.bubbleCheck(A, w)
-- safety check removed, no benefit after new code
local B
if currentTarget then B = currentTarget:getPoint() else B = A end
local d = math.floor(dcsCommon.dist(A, B))
theItem.lastDistance = d -- save it for post mortem
local desc = theItem.weaponName .. ": "
@ -339,22 +239,18 @@ function guardianAngel.monitorItem(theItem)
desc = desc .. "tracking " .. theItem.targetName .. ", d = " .. d .. "m"
local vcc = dcsCommon.getClosingVelocity(t, w)
desc = desc .. ", Vcc = " .. math.floor(vcc) .. "m/s"
-- now calculate lethal distance: vcc is in meters per second
-- and we sample ups times per second
-- making the missile cover vcc / ups meters in the next
-- timer interval. If it now is closer than that, we have to
-- destroy the missile
-- calculate lethal distance: vcc is in meters per second
-- we sample ups times per second
-- making the missile move vcc / ups meters per
-- timer interval. If it now is closer than that, we destroy msl
local lethalRange = math.abs(vcc / guardianAngel.ups) * guardianAngel.safetyFactor
desc = desc .. ", LR= " .. math.floor(lethalRange) .. "m"
theItem.lastDesc = desc
theItem.timeStamp = timer.getTime()
theItem.timeStamp = timer.getTime()
if guardianAngel.intervention and
d <= lethalRange + 10
then
desc = desc .. " ANGEL INTERVENTION"
if guardianAngel.announcer and ID then
if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
@ -372,24 +268,16 @@ function guardianAngel.monitorItem(theItem)
end
guardianAngel.invokeCallbacks("intervention", theItem.targetName, theItem.weaponName)
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
local xP = guardianAngel.calcSafeExplosionPoint(A,B, guardianAngel.fxDistance)
trigger.action.explosion(xP, guardianAngel.explosion)
end
return false -- remove from list
end
if guardianAngel.intervention and
d <= guardianAngel.minMissileDist -- god's override
then
desc = desc .. " GOD INTERVENTION"
--if theItem.lostTrack then desc = desc .. " (little sneak!)" end
--if theItem.missed then desc = desc .. " (missed you!)" end
if guardianAngel.intervention and d <= guardianAngel.minMissileDist then -- god's override
desc = desc .. " GOD INTERVENTION"
if guardianAngel.announcer and ID then
if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
@ -405,26 +293,16 @@ function guardianAngel.monitorItem(theItem)
end
return false -- remove from list
end
else
end
return true
return true -- keep in list
end
function guardianAngel.monitorMissiles()
local newArray = {} -- we collect all still existing missiles here
-- and replace missilesInTheAir with that for next round
local newArray = {} -- monitor existing msl
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
local stillAlive = guardianAngel.monitorItem(anItem)
if stillAlive then
table.insert(newArray, anItem)
end
if stillAlive then table.insert(newArray, anItem) end
end
guardianAngel.missilesInTheAir = newArray
end
@ -432,154 +310,60 @@ end
function guardianAngel.filterItem(theItem)
local w = theItem.theWeapon
if not w then return false end
if not w:isExist() then
return false
end
if not w:isExist() then return false end
return true -- missile still alive
end
function guardianAngel.filterMissiles()
local newArray = {} -- we collect all still existing missiles here
-- and replace missilesInTheAir with that for next round
local newArray = {} -- filter msl
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
local stillAlive = guardianAngel.filterItem(anItem)
if stillAlive then
table.insert(newArray, anItem)
end
if stillAlive then table.insert(newArray, anItem) end
end
guardianAngel.missilesInTheAir = newArray
end
--
-- 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)
for idx, theZone in pairs(guardianAngel.angelicZones) do
if cfxZones.unitInZone(theUnit, theZone) then
return theZone
end
if cfxZones.unitInZone(theUnit, theZone) then return theZone end
end
return nil
end
-- event callback from dcsCommon event handler. preProcessor has returned true
function guardianAngel.somethingHappened(event)
-- when this is invoked, the preprocessor guarantees that
-- it's an interesting event and has initiator
function guardianAngel:onEvent(event)
if not event.initiator then return end
local ID = event.id
local theUnit = event.initiator
-- make sure that this is a cat 0 or cat 1
local theUnit = event.initiator
local playerName = nil
if theUnit.getPlayerName then
playerName = theUnit:getPlayerName() -- nil if not a player
end
if theUnit.getPlayerName then playerName = theUnit:getPlayerName() end
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 theZone then
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
if ID == 15 then
-- AI/player 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
-- 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
local cat = theUnit:getCategory()
--trigger.action.outText("birth event for " .. theUnit:getName() .. " with cat = " .. cat, 30)
if cat ~= 1 then
-- not a unit, bye bye
return
end
if not theUnit.getCategory then return end
local theGroup = theUnit:getGroup()
if not theGroup then return end
local gCat = theGroup:getCategory()
if gCat == 0 or gCat == 1 then
--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
if gCat ~= 0 and gCat ~= 1 then return end -- only fixed and rotor wing
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 .." -- 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
if mustProtect then
guardianAngel.addUnitToWatch(theUnit)
end
if playerName and guardianAngel.autoAddPlayer then mustProtect = true end
if mustProtect then guardianAngel.addUnitToWatch(theUnit) end
return
end
if ID == 21 and playerName then
if ID == 21 and playerName then -- player leave unit
guardianAngel.removeUnitToWatch(theUnit)
return
end
@ -589,7 +373,6 @@ function guardianAngel.somethingHappened(event)
return
end
if ID == 1 then
-- even if not active, we collect missile data
-- someone shot something. see if it is fire directed at me
@ -597,12 +380,8 @@ function guardianAngel.somethingHappened(event)
local theTarget
if theWeapon then
theTarget = theWeapon:getTarget()
else
return
end
if not theTarget then
return
end
else return end
if not theTarget then return end
if not theTarget:isExist() then return end
-- if we get here, we have weapon aimed at a target
@ -611,17 +390,13 @@ function guardianAngel.somethingHappened(event)
local launcher = theUnit
guardianAngel.missilesAndTargets[theWeapon:getName()] = targetName
if not watchedUnit then
-- we may still want to watch this if the missile
-- can be re-targeted
if guardianAngel.verbose then
trigger.action.outText("+++gA: missile <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">, not a threat", 30)
end
if guardianAngel.verbose then trigger.action.outText("+++gA: missile <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">, not a threat", 30) end
-- add it as no threat
local theQItem = guardianAngel.createQItem(theWeapon, theTarget, false, launcher) -- this is not a threat, simply watch for re-target
table.insert(guardianAngel.missilesInTheAir, theQItem)
return
end -- fired at some other poor sucker, we don't care
return -- fired at some other poor sucker, we don't care
end
-- if we get here, someone fired a guided weapon at my watched units
-- create a new item for my queue
local theQItem = guardianAngel.createQItem(theWeapon, theTarget, true, launcher) -- this is watched
@ -632,12 +407,9 @@ function guardianAngel.somethingHappened(event)
local A = theWeapon:getPoint()
local B = theTarget:getPoint()
local oclock = dcsCommon.clockPositionOfARelativeToB(A, B, unitHeading)
local grpID = theTarget:getGroup():getID()
local vbInfo = ""
if guardianAngel.verbose then
vbInfo = ", <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">"
end
if guardianAngel.verbose then vbInfo = ", <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">" end
if guardianAngel.launchWarning and guardianAngel.active then
-- currently, we always detect immediately
-- can be moved to update()
@ -654,13 +426,12 @@ function guardianAngel.somethingHappened(event)
trigger.action.outSound(fileName)
end
end
theQItem.detected = true -- remember: we detected and warned already
end
return
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.intervention then return end -- we don't intervene
if not event.weapon then return end -- no weapon, no interest
@ -673,19 +444,14 @@ function guardianAngel.somethingHappened(event)
local theProtegee = nil
for idx, aProt in pairs(guardianAngel.unitsToWatchOver) do
if aProt:isExist() then
if tName == aProt:getName() then
theProtegee = aProt
end
if tName == aProt:getName() then theProtegee = aProt end
else
if guardianAngel.verbose then
trigger.action.outText("+++gA: whoops. Looks like I lost a wing there... sorry", 30)
end
if guardianAngel.verbose then trigger.action.outText("+++gA: Whoops. Looks like I lost a wing there... sorry", 30) end
end
end
if not theProtegee then return end
-- one of our protegees was hit
--trigger.action.outText("+++gA: Protegee " .. tName .. " was hit", 30)
-- one of our protegees was hit
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
trigger.action.outText("+++gA: <" .. wName .. "> was originally aimed at <" .. guardianAngel.missilesAndTargets[wName] .. ">", 30)
@ -696,63 +462,49 @@ function guardianAngel.somethingHappened(event)
local wpnTgtName = "(none???)"
if wpnTgt then wpnTgtName = wpnTgt:getName() end
trigger.action.outText("+++gA: *current* weapon's target is <" .. wpnTgtName .. ">", 30)
if wpnTgtName ~= tName then
trigger.action.outText("+++gA: COLLATERAL DAMAGE!", 30)
end
if wpnTgtName ~= tName then trigger.action.outText("+++gA: COLLATERAL DAMAGE!", 30) end
end
else
trigger.action.outText("***gA: no missile in the air for <" .. wName .. ">!!!!", 30)
end
-- let's see if the victim was in our list of protected
-- units
-- let's see if the victim was in our list of protected units
local thePerp = nil
for idx, anItem in pairs(guardianAngel.missilesInTheAir) do
if anItem.weaponName == wName then
thePerp = anItem
end
if anItem.weaponName == wName then thePerp = anItem end
end
if not thePerp then return end
--trigger.action.outText("+++gA: offender was known to gA: " .. wName, 30)
-- stats only: do target and intended target match?
local theWTarget = theWeapon:getTarget()
if not theWTarget then return end -- no target no interest
local wtName = theWTarget:getName()
if wtName == tName then
trigger.action.outText("+++gA: perp's ill intent confirmed", 30)
else
trigger.action.outText("+++gA: UNINTENDED CONSEQUENCES", 30)
if wtName == tName then trigger.action.outText("+++gA: perp's ill intent confirmed", 30)
else trigger.action.outText("+++gA: UNINTENDED CONSEQUENCES", 30)
end
-- if we should have protected: mea maxima culpa
trigger.action.outText("[+++gA: Angel hangs her head in shame. Mea Culpa, " .. tName.."]", 30)
-- see if we can find the q item
local missedItem = guardianAngel.getQItemForWeaponNamed(wName)
if not missedItem then
trigger.action.outText("Cannot retrieve item for <" .. wName .. ">", 30)
if not missedItem then trigger.action.outText("Cannot retrieve item for <" .. wName .. ">", 30)
else
local now = timer.getTime()
local delta = now - missedItem.timeStamp
local wasThreat = dcsCommon.bool2YesNo(missedItem.threat)
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)
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)
end
return
end
local myType = theUnit:getTypeName()
if guardianAngel.verbose then
local myType = theUnit:getTypeName()
trigger.action.outText("+++gA: event " .. ID .. " for unit " .. theUnit:getName() .. " of type " .. myType, 30)
end
end
--
-- U P D A T E L O O P
--
function guardianAngel.update()
timer.scheduleFunction(guardianAngel.update, {}, timer.getTime() + 1/guardianAngel.ups)
-- and break off if nothing to do
@ -760,53 +512,85 @@ function guardianAngel.update()
guardianAngel.filterMissiles()
return
end
guardianAngel.monitorMissiles()
end
function guardianAngel.doActivate()
guardianAngel.active = true
if guardianAngel.verbose or guardianAngel.announcer then
trigger.action.outText("Guardian Angel has activated", 30)
end
if guardianAngel.verbose or guardianAngel.announcer then trigger.action.outText("Guardian Angel has activated", 30) end
end
function guardianAngel.doDeActivate()
guardianAngel.active = false
if guardianAngel.verbose or guardianAngel.announcer then
trigger.action.outText("Guardian Angel NO LONGER ACTIVE", 30)
end
if guardianAngel.verbose or guardianAngel.announcer then trigger.action.outText("Guardian Angel NO LONGER ACTIVE", 30) end
end
function guardianAngel.flagUpdate()
timer.scheduleFunction(guardianAngel.flagUpdate, {}, timer.getTime() + 1) -- once every second
-- check the flags for on/off
if guardianAngel.activate then
if cfxZones.testZoneFlag(guardianAngel, guardianAngel.activate, "change","lastActivate") then
if cfxZones.testZoneFlag(guardianAngel, guardianAngel.activate, "change","lastActivate") then
guardianAngel.doActivate()
end
end
if guardianAngel.deactivate then
if cfxZones.testZoneFlag(guardianAngel, guardianAngel.deactivate, "change","lastDeActivate") then
if cfxZones.testZoneFlag(guardianAngel, guardianAngel.deactivate, "change","lastDeActivate") then
guardianAngel.doDeActivate()
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
function guardianAngel.collectPlayerUnits()
-- make sure we have all existing player units
-- at start of game
for i=1, 2 do
-- currently only two factions in dcs
local factionUnits = coalition.getPlayers(i)
for idx, theUnit in pairs(factionUnits) do
local mustProtect = false
if guardianAngel.autoAddPlayers then
mustProtect = true
end
if guardianAngel.autoAddPlayer then mustProtect = true end
theZone = guardianAngel.getAngelicZoneForUnit(theUnit)
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)
end
end
if mustProtect then
guardianAngel.addUnitToWatch(theUnit)
end
if mustProtect then guardianAngel.addUnitToWatch(theUnit) 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)
end
end
if mustProtect then
guardianAngel.addUnitToWatch(theUnit)
end
if mustProtect then guardianAngel.addUnitToWatch(theUnit) end
end
end
end
@ -863,10 +640,7 @@ function guardianAngel.readConfigZone()
if not theZone then
theZone = cfxZones.createSimpleZone("guardianAngelConfig")
end
guardianAngel.verbose = theZone:getBoolFromZoneProperty("verbose", false)
guardianAngel.verbose = theZone.verbose
guardianAngel.autoAddPlayer = theZone:getBoolFromZoneProperty("autoAddPlayer", true)
guardianAngel.launchWarning = theZone:getBoolFromZoneProperty("launchWarning", true)
guardianAngel.intervention = theZone:getBoolFromZoneProperty("intervention", true)
@ -876,9 +650,7 @@ function guardianAngel.readConfigZone()
guardianAngel.fxDistance = theZone:getNumberFromZoneProperty( "fxDistance", 500)
guardianAngel.active = theZone:getBoolFromZoneProperty("active", true)
guardianAngel.msgTime = theZone:getNumberFromZoneProperty("msgTime", 30)
if theZone:hasProperty("activate?") then
guardianAngel.activate = theZone:getStringFromZoneProperty("activate?", "*<none>")
guardianAngel.lastActivate = theZone:getFlagValue(guardianAngel.activate)
@ -895,28 +667,17 @@ function guardianAngel.readConfigZone()
guardianAngel.lastDeActivate = theZone:getFlagValue(guardianAngel.deactivate)
end
if theZone:hasProperty("launchSound") then
guardianAngel.launchSound = theZone:getStringFromZoneProperty("launchSound", "nosound")
end
if theZone:hasProperty("launchSound") then guardianAngel.launchSound = theZone:getStringFromZoneProperty("launchSound", "nosound") end
if theZone:hasProperty("interventionSound") then
guardianAngel.interventionSound = theZone:getStringFromZoneProperty("interventionSound", "nosound")
end
if theZone:hasProperty("interventionSound") then guardianAngel.interventionSound = theZone:getStringFromZoneProperty("interventionSound", "nosound") end
guardianAngel.configZone = theZone
if guardianAngel.verbose then
trigger.action.outText("+++gA: processed config zone", 30)
end
end
--
-- guardian zones
-- guardian/sanctuary zones
--
function guardianAngel.processGuardianZone(theZone)
theZone.angelic = theZone:getBoolFromZoneProperty("guardian", true)
theZone.angelic = true -- theZone:getBoolFromZoneProperty("guardian", true)
if theZone.verbose or guardianAngel.verbose then
trigger.action.outText("+++gA: processed 'guardian' zone <" .. theZone.name .. ">", 30)
end
@ -926,50 +687,48 @@ end
function guardianAngel.readGuardianZones()
local attrZones = cfxZones.getZonesWithAttributeNamed("guardian")
for k, aZone in pairs(attrZones) do
guardianAngel.processGuardianZone(aZone)
end
for k, aZone in pairs(attrZones) do guardianAngel.processGuardianZone(aZone) 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
--
function guardianAngel.start()
-- lib check
if not dcsCommon.libCheck("cfx Guardian Angel",
guardianAngel.requiredLibs) then
return false
end
if not dcsCommon.libCheck("cfx Guardian Angel", guardianAngel.requiredLibs) then return false end
-- read config
guardianAngel.readConfigZone()
-- read guarded zones
guardianAngel.readGuardianZones()
-- install event monitor
dcsCommon.addEventHandler(guardianAngel.somethingHappened,
guardianAngel.preProcessor,
guardianAngel.postProcessor)
guardianAngel.readSantuaryZones()
-- insert into evet loop
world.addEventHandler(guardianAngel)
-- collect all units that are already in the game at this point
guardianAngel.collectPlayerUnits()
guardianAngel.collectPlayerUnits(guardianAngel)
guardianAngel.collectAIUnits()
-- start update
-- start update for missiles
guardianAngel.update()
-- start flag check
guardianAngel.flagUpdate()
-- start flag/sanctuary checks
guardianAngel.flagUpdate()
trigger.action.outText("Guardian Angel v" .. guardianAngel.version .. " running", 30)
return true
end
function guardianAngel.testCB(reason, targetName, weaponName)
trigger.action.outText("gA - CB for ".. reason .. ": " .. targetName .. " w: " .. weaponName, guardianAngel.msgTime)
end
-- go go go
if not guardianAngel.start() then
trigger.action.outText("Loading Guardian Angel failed.", 30)
@ -979,4 +738,7 @@ end
-- test callback
--guardianAngel.addCallback(guardianAngel.testCB)
--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.version = "4.2.0"
cfxHeloTroops.version = "4.2.1"
cfxHeloTroops.verbose = false
cfxHeloTroops.autoDrop = true
cfxHeloTroops.autoPickup = false
@ -19,6 +19,8 @@ cfxHeloTroops.requestRange = 500 -- meters
- code cleanup
4.2.0 - support for individual lase codes
- support for drivable
4.2.1 - increased verbosity
- also supports 'pickupRang" for reverse-compatibility with manual typo.
--]]--
cfxHeloTroops.minTime = 3 -- seconds beween tandings
@ -492,9 +494,11 @@ function cfxHeloTroops.addGroundMenu(conf)
end
function cfxHeloTroops.filterTroopsByType(unitsToLoad)
if cfxHeloTroops.verbose then trigger.action.outText("+++heloT: enter filterTroops", 30) end
local filteredGroups = {}
for idx, aTeam in pairs(unitsToLoad) do
local group = aTeam.group
if cfxHeloTroops.verbose then trigger.action.outText("+++heloT: testing group <" .. group:getName() .. ">", 30) end
local theTypes = dcsCommon.getGroupTypeString(group)
local aT = dcsCommon.splitString(theTypes, ",")
@ -504,11 +508,19 @@ function cfxHeloTroops.filterTroopsByType(unitsToLoad)
if cfxHeloTroops.legalTroops then
if not dcsCommon.arrayContainsString(cfxHeloTroops.legalTroops, sT) then
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
end
else
if not dcsCommon.typeIsInfantry(sT) then
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
end
end
@ -519,13 +531,24 @@ function cfxHeloTroops.filterTroopsByType(unitsToLoad)
-- this one is managed by csarManager,
-- don't load it for helo troops
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
if pass then
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
if cfxHeloTroops.verbose then
trigger.action.outText("+++heloT[menu]: returning with <" .. #filteredGroups .. "> available groups", 30)
end
return filteredGroups
end
@ -981,6 +1004,9 @@ function cfxHeloTroops.readConfigZone()
cfxHeloTroops.autoDrop = theZone:getBoolFromZoneProperty("autoDrop", false)
cfxHeloTroops.autoPickup = theZone:getBoolFromZoneProperty("autoPickup", false)
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.actionSound = theZone:getStringFromZoneProperty("actionSound", "Quest Snare 3.wav")

View File

@ -144,7 +144,7 @@ function jtacGrpUI.doCommandX(args)
local targetList = jtacGrpUI.collectJTACtargets(conf, true)
-- iterate the list
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)
return
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.version = "3.0.1"
cfxPlayerScoreUI.verbose = false
cfxPlayerScoreUI.version = "3.1.0"
--[[-- 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.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 = {
"dcsCommon", -- this is doing score keeping
"cfxZones", -- zones for config
"cfxPlayerScore",
}
cfxPlayerScoreUI.soundFile = "Quest Snare 3.wav"
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
function cfxPlayerScoreUI.redirectCommandX(args)
@ -38,20 +29,16 @@ function cfxPlayerScoreUI.doCommandX(args)
if not theGroup then return end -- should not happen
local gid = theGroup:getID()
local coa = theGroup:getCoalition()
if not cfxPlayerScore.scoreTextForPlayerNamed then
trigger.action.outText("***pSGUI: CANNOT FIND PlayerScore MODULE", 30)
return
end
local desc = ""
if what == "score" then
desc = cfxPlayerScore.scoreTextForPlayerNamed(playerName)
elseif what == "allMySide" then
desc = cfxPlayerScore.scoreSummaryForPlayersOfCoalition(coa)
if what == "score" then desc = cfxPlayerScore.scoreTextForPlayerNamed(playerName)
elseif what == "allMySide" then desc = cfxPlayerScore.scoreSummaryForPlayersOfCoalition(coa)
elseif what == "all" then
desc = "Score Table For All Players:\n" .. cfxPlayerScore.scoreTextForAllPlayers(cfxPlayerScoreUI.ranked)
else
desc = "PlayerScore UI: unknown command <" .. what .. ">"
else desc = "PlayerScore UI: unknown command <" .. what .. ">"
end
trigger.action.outTextForGroup(gid, desc, 30)
trigger.action.outSoundForGroup(gid, cfxPlayerScoreUI.soundFile)
@ -61,69 +48,83 @@ end
-- event handling: we are only interested in birth events
-- for player aircraft
--
function cfxPlayerScore.processPlayerUnit(theUnit)
function cfxPlayerScoreUI.processPlayerUnit(theUnit)
if not theUnit.getPlayerName then return end -- no player name, bye!
local playerName = theUnit:getPlayerName()
if not playerName then return end
-- so now we know it's a player plane. get group name
-- now we know it's a player unit. get group name
local theGroup = theUnit:getGroup()
local groupName = theGroup:getName()
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
if cfxPlayerScoreUI.rootCommands[groupName] then
-- need re-init to store new pilot name
if cfxPlayerScoreUI.verbose then
trigger.action.outText("++pSGui: group <" .. groupName .. "> already has score menu, removing.", 30)
end
if cfxPlayerScoreUI.verbose then trigger.action.outText("++pSGui: group <" .. groupName .. "> already has score menu, removing.", 30) end
missionCommands.removeItemForGroup(gid, cfxPlayerScoreUI.rootCommands[groupName])
cfxPlayerScoreUI.rootCommands[groupName] = nil
end
-- we need to install a group menu item for scores.
-- will persist through death
-- we install a group menu item for scores.
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"})
commandTxt = "Show my Side Score / Kills"
theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "allMySide"})
if cfxPlayerScoreUI.allowAll then
commandTxt = "Show All Player Scores"
theCommand = missionCommands.addCommandForGroup(gid, commandTxt, theMenu, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "all"})
end
cfxPlayerScoreUI.rootCommands[groupName] = theMenu
if cfxPlayerScoreUI.verbose then
trigger.action.outText("++pSGui: installed player score menu for group <" .. groupName .. ">", 30)
end
end
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
local theUnit = event.initiator
cfxPlayerScore.processPlayerUnit(theUnit)
cfxPlayerScoreUI.processPlayerUnit(theUnit)
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
--
function cfxPlayerScoreUI.start()
if not dcsCommon.libCheck("cfx Player Score UI",
cfxPlayerScoreUI.requiredLibs)
then return false end
-- install the event handler for new player planes
if not dcsCommon.libCheck("cfx Player Score UI", cfxPlayerScoreUI.requiredLibs) then return false end
-- install event handler for new player planes and CA
world.addEventHandler(cfxPlayerScoreUI)
-- process all existing players (late start)
dcsCommon.iteratePlayers(cfxPlayerScore.processPlayerUnit)
cfxPlayerScoreUI.readConfig()
trigger.action.outText("cf/x PlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30)
return true
end
--
-- GO GO GO
--

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
unitZone={}
unitZone.version = "2.0.0"
unitZone.version = "2.0.1"
unitZone.verbose = false
unitZone.ups = 1
unitZone.requiredLibs = {
@ -8,16 +8,6 @@ unitZone.requiredLibs = {
}
--[[--
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)
- lookFor defaults to "*"
- OOP dmlZones
@ -27,6 +17,7 @@ unitZone.requiredLibs = {
- unitZone now used to define the coalition, coalition DEPRECATED
- filter synonym
- direct#, directInv# synonyms
2.0.1 - code hardening
--]]--
unitZone.unitZones = {}
@ -182,15 +173,11 @@ function unitZone.createUnitZone(theZone)
if unitZone.verbose or theZone.verbose then
trigger.action.outText("+++uZne: processsed unit zone <" .. theZone.name .. "> with status = (" .. dcsCommon.bool2Text(theZone.lastStatus) .. ")", 30)
end
end
end
--
-- process zone
--
function unitZone.collectGroups(theZone)
local collector = {} -- players: units, groups: groups
if theZone.matching == "player" then
@ -231,7 +218,6 @@ end
function unitZone.checkZoneStatus(theZone)
-- returns true (at least one unit found in zone)
-- or false (no unit found in zone)
-- collect all groups to inspect
local theGroups = unitZone.collectGroups(theZone)
local lookFor = theZone.lookFor
@ -241,40 +227,44 @@ function unitZone.checkZoneStatus(theZone)
-- we check the names for players only
-- collector holds units for players, not groups
for idx, pUnit in pairs(theGroups) do
local puName = pUnit:getName()
local hasMatch = theZone.matchAll
if not hasMatch then
if theZone.lookForBeginsWith then
hasMatch = dcsCommon.stringStartsWith(puName, lookFor)
else
hasMatch = puName == lookFor
if Unit.isExist(pUnit) then
local puName = pUnit:getName()
local hasMatch = theZone.matchAll
if not hasMatch then
if theZone.lookForBeginsWith then
hasMatch = dcsCommon.stringStartsWith(puName, lookFor)
else
hasMatch = puName == lookFor
end
end
if hasMatch then
if cfxZones.unitInZone(pUnit, theZone) then
return true
end
end
end
if hasMatch then
if cfxZones.unitInZone(pUnit, theZone) then
return true
end
end
end
else
-- we perform group check.
for idx, aGroup in pairs(theGroups) do
local gName=aGroup:getName()
local hasMatch = theZone.matchAll
if not hasMatch then
if theZone.lookForBeginsWith then
hasMatch = dcsCommon.stringStartsWith(gName, lookFor)
else
hasMatch = gName == lookFor
end
end
if hasMatch and aGroup:isExist() then
-- check all living units in zone
local gUnits = aGroup:getUnits()
for idy, aUnit in pairs (gUnits) do
if cfxZones.unitInZone(aUnit, theZone) then
return true
if Group.isExist(aGroup) then
local gName=aGroup:getName()
local hasMatch = theZone.matchAll
if not hasMatch then
if theZone.lookForBeginsWith then
hasMatch = dcsCommon.stringStartsWith(gName, lookFor)
else
hasMatch = gName == lookFor
end
end
if hasMatch and aGroup:isExist() then
-- check all living units in zone
local gUnits = aGroup:getUnits()
for idy, aUnit in pairs (gUnits) do
if cfxZones.unitInZone(aUnit, theZone) then
return true
end
end
end
end
@ -282,7 +272,6 @@ function unitZone.checkZoneStatus(theZone)
end
return false
end
--
-- update
--
@ -365,7 +354,6 @@ function unitZone.update()
end
end
end
--
-- Config & Start
--
@ -375,11 +363,8 @@ function unitZone.readConfigZone()
if not theZone then
theZone = cfxZones.createSimpleZone("unitZoneConfig")
end
unitZone.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
unitZone.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 1)
if unitZone.verbose then
trigger.action.outText("+++uZne: read config", 30)
end
@ -394,20 +379,16 @@ function unitZone.start()
if not dcsCommon.libCheck("cfx Unit Zone", unitZone.requiredLibs) then
return false
end
-- read config
unitZone.readConfigZone()
-- process cloner Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("unitZone")
for k, aZone in pairs(attrZones) do
unitZone.createUnitZone(aZone) -- process attributes
unitZone.addUnitZone(aZone) -- add to list
end
-- start update
unitZone.update()
trigger.action.outText("cfx Unit Zone v" .. unitZone.version .. " started.", 30)
return true
end
@ -418,6 +399,5 @@ if not unitZone.start() then
unitZone = nil
end
--ToDo: add 'neutral' support and add 'both' option
--ToDo: add API

Binary file not shown.

Binary file not shown.