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,7 +178,6 @@ function delayFlag.update()
aZone:pollFlag(aZone.delayDoneFlag, aZone.delayMethod)
end
end
aZone:setFlagValue(aZone.delayTimeLeft, remaining)
else
-- we are paused. Check for 'continue'
@ -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,15 +251,8 @@ 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
@ -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
@ -308,30 +218,20 @@ function guardianAngel.monitorItem(theItem)
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
-- 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()
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
if guardianAngel.intervention and d <= guardianAngel.minMissileDist then -- god's override
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.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,113 +310,47 @@ 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 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)
if gCat ~= 0 and gCat ~= 1 then return end -- only fixed and rotor wing
theZone = guardianAngel.getAngelicZoneForUnit(theUnit)
if theZone then
mustProtect = theZone.angelic
@ -546,40 +358,12 @@ function guardianAngel.somethingHappened(event)
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
if playerName and guardianAngel.autoAddPlayer then mustProtect = true end
if mustProtect then guardianAngel.addUnitToWatch(theUnit) 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)
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
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)
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,28 +512,22 @@ 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
guardianAngel.doActivate()
@ -793,20 +539,58 @@ function guardianAngel.flagUpdate()
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
-- 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
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 = {}
@ -183,14 +174,10 @@ 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
--
-- 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,6 +227,7 @@ 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
if Unit.isExist(pUnit) then
local puName = pUnit:getName()
local hasMatch = theZone.matchAll
if not hasMatch then
@ -256,10 +243,12 @@ function unitZone.checkZoneStatus(theZone)
end
end
end
end
else
-- we perform group check.
for idx, aGroup in pairs(theGroups) do
if Group.isExist(aGroup) then
local gName=aGroup:getName()
local hasMatch = theZone.matchAll
if not hasMatch then
@ -280,9 +269,9 @@ function unitZone.checkZoneStatus(theZone)
end
end
end
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.