Version 1.0

One Dot Oh!
This commit is contained in:
Christian Franz 2022-06-16 21:22:34 +02:00
parent 1e5cb8d35e
commit 92dc6ca40f
15 changed files with 1091 additions and 130 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
cfxArtilleryZones = {} cfxArtilleryZones = {}
cfxArtilleryZones.version = "2.2.0" cfxArtilleryZones.version = "2.2.1"
cfxArtilleryZones.requiredLibs = { cfxArtilleryZones.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
@ -29,6 +29,7 @@ cfxArtilleryZones.verbose = false
2.1.0 - DML Flag Support 2.1.0 - DML Flag Support
- code cleanup - code cleanup
2.2.0 - DML Watchflag integration 2.2.0 - DML Watchflag integration
2.2.1 - minor code clean-up
Artillery Target Zones *** EXTENDS ZONES *** Artillery Target Zones *** EXTENDS ZONES ***
Target Zones for artillery. Can determine which zones are in range and visible and then handle artillery barrage to this zone Target Zones for artillery. Can determine which zones are in range and visible and then handle artillery barrage to this zone
@ -136,11 +137,7 @@ function cfxArtilleryZones.processArtilleryZone(aZone)
if cfxZones.hasProperty(aZone, "f?") then if cfxZones.hasProperty(aZone, "f?") then
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "f?", "none") aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "f?", "none")
end end
--[[--
if cfxZones.hasProperty(aZone, "triggerFlag") then
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "triggerFlag", "none")
end
--]]--
if cfxZones.hasProperty(aZone, "artillery?") then if cfxZones.hasProperty(aZone, "artillery?") then
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "artillery?", "none") aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "artillery?", "none")
end end

View File

@ -1,11 +1,16 @@
cfxReconMode = {} cfxReconMode = {}
cfxReconMode.version = "1.5.0" cfxReconMode.version = "2.0.0"
cfxReconMode.verbose = false -- set to true for debug info cfxReconMode.verbose = false -- set to true for debug info
cfxReconMode.reconSound = "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" -- to be played when somethiong discovered cfxReconMode.reconSound = "UI_SCI-FI_Tone_Bright_Dry_20_stereo.wav" -- to be played when somethiong discovered
cfxReconMode.prioList = {} -- group names that are high prio and generate special event cfxReconMode.prioList = {} -- group names that are high prio and generate special event
cfxReconMode.blackList = {} -- group names that are NEVER detected. Comma separated strings, e.g. {"Always Hidden", "Invisible Group"} cfxReconMode.blackList = {} -- group names that are NEVER detected. Comma separated strings, e.g. {"Always Hidden", "Invisible Group"}
cfxReconMode.dynamics = {} -- if a group name is dynamic
cfxReconMode.zoneInfo = {} -- additional zone info
cfxReconMode.scoutZones = {} -- zones that define aircraft. used for late eval of players
cfxReconMode.allowedScouts = {} -- when not using autoscouts
cfxReconMode.blindScouts = {} -- to exclude aircraft from being scouts
cfxReconMode.removeWhenDestroyed = true cfxReconMode.removeWhenDestroyed = true
cfxReconMode.activeMarks = {} -- all marks and their groups, indexed by groupName cfxReconMode.activeMarks = {} -- all marks and their groups, indexed by groupName
@ -48,6 +53,26 @@ VERSION HISTORY
1.5.0 - removeWhenDestroyed() 1.5.0 - removeWhenDestroyed()
- autoRemove() - autoRemove()
- readConfigZone creates default config zone so we get correct defaulting - readConfigZone creates default config zone so we get correct defaulting
2.0.0 - DML integration prio+-->prio! detect+ --> detect!
and method
- changed access to prio and blacklist to hash
- dynamic option for prio and black
- trigger zones for designating prio and blacklist
- reworked stringInList to also include dynamics
- Report in SALT format: size, action, loc, time.
- Marks add size, action info
- LatLon or MGRS
- MGRS option in config
- filter onEvent for helo and aircraft
- allowedScouts and blind
- stronger scout filtering at startup
- better filtering on startup when autorecon and playeronly
- player lazy late checking, zone saving
- correct checks when not autorecon
- ability to add special flags to recon prio group
- event guard in onEvent
- <t> wildcard
- <lat>, <lon>, <mgrs> wildcards
cfxReconMode is a script that allows units to perform reconnaissance cfxReconMode is a script that allows units to perform reconnaissance
missions and, after detecting units, marks them on the map with missions and, after detecting units, marks them on the map with
@ -79,7 +104,9 @@ cfxReconMode.playerOnlyRecon = false -- only players can do recon
cfxReconMode.reportNumbers = true -- also add unit count in report cfxReconMode.reportNumbers = true -- also add unit count in report
cfxReconMode.prioFlag = nil cfxReconMode.prioFlag = nil
cfxReconMode.detectFlag = nil cfxReconMode.detectFlag = nil
cfxReconMode.method = "inc"
cfxReconMode.applyMarks = true cfxReconMode.applyMarks = true
cfxReconMode.mgrs = false
cfxReconMode.ups = 1 -- updates per second. cfxReconMode.ups = 1 -- updates per second.
cfxReconMode.scouts = {} -- units that are performing scouting. cfxReconMode.scouts = {} -- units that are performing scouting.
@ -111,38 +138,80 @@ function cfxReconMode.invokeCallbacks(reason, theSide, theScout, theGroup, theNa
end end
-- add a priority/blackList group name to prio list -- add a priority/blackList group name to prio list
function cfxReconMode.addToPrioList(aGroup) function cfxReconMode.addToPrioList(aGroup, dynamic)
if not dynamic then dynamic = false end
if not aGroup then return end if not aGroup then return end
if type(aGroup) == "table" and aGroup.getName then if type(aGroup) == "table" and aGroup.getName then
aGroup = aGroup:getName() aGroup = aGroup:getName()
end end
if type(aGroup) == "string" then if type(aGroup) == "string" then
table.insert(cfxReconMode.prioList, aGroup) -- table.insert(cfxReconMode.prioList, aGroup)
cfxReconMode.prioList[aGroup] = aGroup
cfxReconMode.dynamics[aGroup] = dynamic
end end
end end
function cfxReconMode.addToBlackList(aGroup) function cfxReconMode.addToBlackList(aGroup, dynamic)
if not dynamic then dynamic = false end
if not aGroup then return end if not aGroup then return end
if type(aGroup) == "table" and aGroup.getName then if type(aGroup) == "table" and aGroup.getName then
aGroup = aGroup:getName() aGroup = aGroup:getName()
end end
if type(aGroup) == "string" then if type(aGroup) == "string" then
table.insert(cfxReconMode.blackList, aGroup) --table.insert(cfxReconMode.blackList, aGroup)
cfxReconMode.blackList[aGroup] = aGroup
cfxReconMode.dynamics[aGroup] = dynamic
end end
end end
function cfxReconMode.addToAllowedScoutList(aGroup, dynamic)
if not dynamic then dynamic = false end
if not aGroup then return end
if type(aGroup) == "table" and aGroup.getName then
aGroup = aGroup:getName()
end
if type(aGroup) == "string" then
cfxReconMode.allowedScouts[aGroup] = aGroup
cfxReconMode.dynamics[aGroup] = dynamic
end
end
function cfxReconMode.addToBlindScoutList(aGroup, dynamic)
if not dynamic then dynamic = false end
if not aGroup then return end
if type(aGroup) == "table" and aGroup.getName then
aGroup = aGroup:getName()
end
if type(aGroup) == "string" then
cfxReconMode.blindScouts[aGroup] = aGroup
cfxReconMode.dynamics[aGroup] = dynamic
end
end
function cfxReconMode.isStringInList(theString, theList) function cfxReconMode.isStringInList(theString, theList)
if not theString then return false end -- returns two values: inList, and original group name (if exist)
if not theList then return false end if not theString then return false, nil end
if type(theString) == "string" then if type(theString) ~= "string" then return false, nil end
for idx,anItem in pairs(theList) do if not theList then return false, nil end
if anItem == theString then return true end
-- first, try a direct look-up. if this produces a hit
-- we directly return true
if theList[theString] then return true, theString end
-- now try the more involved retrieval with string starts with
for idx, aName in pairs(theList) do
if dcsCommon.stringStartsWith(theString, aName) then
-- they start the same. are dynamics allowed?
if cfxReconMode.dynamics[aName] then
return true, aName
end
end end
end end
return false
return false, nil
end end
-- addScout directly adds a scout unit. Use from external -- addScout directly adds a scout unit. Use from external
-- to manually add a unit (e.g. via GUI when autoscout isExist -- to manually add a unit (e.g. via GUI when autoscout isExist
-- off, or to force a scout unit (e.g. when scouts for a side -- off, or to force a scout unit (e.g. when scouts for a side
@ -205,7 +274,7 @@ end
function cfxReconMode.removeScout(theUnit) function cfxReconMode.removeScout(theUnit)
if not theUnit then if not theUnit then
trigger.action.outText("+++cfxRecon: WARNING - nil Unit on remove", 30) trigger.action.outText("+++rcn: WARNING - nil Unit on remove", 30)
return return
end end
@ -265,7 +334,8 @@ function cfxReconMode.placeMarkForUnit(location, theSide, theGroup)
local theID = cfxReconMode.uuid() local theID = cfxReconMode.uuid()
local theDesc = "Contact: "..theGroup:getName() local theDesc = "Contact: "..theGroup:getName()
if cfxReconMode.reportNumbers then if cfxReconMode.reportNumbers then
theDesc = theDesc .. " (" .. theGroup:getSize() .. " units)" -- theDesc = theDesc .. " (" .. theGroup:getSize() .. " units)"
theDesc = theDesc .. " - " .. cfxReconMode.getSit(theGroup) .. ", " .. cfxReconMode.getAction(theGroup) .. "."
end end
trigger.action.markToCoalition( trigger.action.markToCoalition(
theID, theID,
@ -296,6 +366,121 @@ function cfxReconMode.removeMarkForArgs(args)
cfxReconMode.detectedGroups[theName] = nil -- some housekeeping. cfxReconMode.detectedGroups[theName] = nil -- some housekeeping.
end end
function cfxReconMode.getSit(theGroup)
local msg = ""
-- analyse the group we just discovered. We know it's a ground troop, so simply differentiate between vehicles and infantry
local theUnits = theGroup:getUnits()
local numInf = 0
local numVehicles = 0
for idx, aUnit in pairs(theUnits) do
if dcsCommon.unitIsInfantry(aUnit) then
numInf = numInf + 1
else
numVehicles = numVehicles + 1
end
end
if numInf > 0 and numVehicles > 0 then
-- mixed infantry and vehicles
msg = numInf .. " infantry and " .. numVehicles .. " vehicles"
elseif numInf > 0 then
-- only infantry
msg = numInf .. " infantry"
else
-- only vehicles
msg = numVehicles .. " vehicles"
end
return msg
end
function cfxReconMode.getAction(theGroup)
local msg = ""
-- simply get the first unit and get velocity vector.
-- if it's smaller than 1 m/s (= 3.6 kmh), it's "Guarding", if it's faster, it's
-- moving with direction
local theUnit = theGroup:getUnit(1)
local vvel = theUnit:getVelocity()
local vel = dcsCommon.vMag(vvel)
if vel < 1 then
msg = "apparently guarding"
else
local speed = ""
if vel < 3 then speed = "slowly"
elseif vel < 6 then speed = "deliberately"
else speed = "briskly" end
local heading = dcsCommon.getUnitHeading(theUnit) -- in rad
msg = speed .. " moving " .. dcsCommon.bearing2compass(heading)
end
return msg
end
function cfxReconMode.getLocation(theGroup)
local msg = ""
local theUnit = theGroup:getUnit(1)
local currPoint = theUnit:getPoint()
if cfxReconMode.mgrs then
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
msg = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
else
local lat, lon, alt = coord.LOtoLL(currPoint)
lat, lon = dcsCommon.latLon2Text(lat, lon)
msg = "Lat " .. lat .. " Lon " .. lon
end
return msg
end
function cfxReconMode.getTimeData()
local msg = ""
local absSecs = timer.getAbsTime()-- + env.mission.start_time
while absSecs > 86400 do
absSecs = absSecs - 86400 -- subtract out all days
end
msg = dcsCommon.processHMS("<:h>:<:m>:<:s>", absSecs)
return "at " .. msg
end
function cfxReconMode.generateSALT(theScout, theGroup)
local msg = theScout:getName() .. " reports new ground contact " .. theGroup:getName() .. ":\n"
-- SALT: S = Situation or number of units A = action they are doing L = Location T = Time
msg = msg .. cfxReconMode.getSit(theGroup) .. ", "-- S
msg = msg .. cfxReconMode.getAction(theGroup) .. ", " -- A
msg = msg .. cfxReconMode.getLocation(theGroup) .. ", " -- L
msg = msg .. cfxReconMode.getTimeData() -- T
return msg
end
function cfxReconMode.processZoneMessage(inMsg, theZone)
if not inMsg then return "<nil inMsg>" end
local formerType = type(inMsg)
if formerType ~= "string" then inMsg = tostring(inMsg) end
if not inMsg then inMsg = "<inMsg is incompatible type " .. formerType .. ">" end
local outMsg = ""
-- replace line feeds
outMsg = inMsg:gsub("<n>", "\n")
if theZone then
outMsg = outMsg:gsub("<z>", theZone.name)
end
-- replace <t> with current mission time HMS
local absSecs = timer.getAbsTime()-- + env.mission.start_time
while absSecs > 86400 do
absSecs = absSecs - 86400 -- subtract out all days
end
local timeString = dcsCommon.processHMS("<:h>:<:m>:<:s>", absSecs)
outMsg = outMsg:gsub("<t>", timeString)
-- replace <lat> with lat of zone point and <lon> with lon of zone point
-- and <mgrs> with mgrs coords of zone point
local currPoint = cfxZones.getPoint(theZone)
local lat, lon, alt = coord.LOtoLL(currPoint)
lat, lon = dcsCommon.latLon2Text(lat, lon)
outMsg = outMsg:gsub("<lat>", lat)
outMsg = outMsg:gsub("<lon>", lon)
currPoint = cfxZones.getPoint(theZone)
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
outMsg = outMsg:gsub("<mgrs>", mgrs)
return outMsg
end
function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc) function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
-- put a mark on the map -- put a mark on the map
@ -312,26 +497,51 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
-- say something -- say something
if cfxReconMode.announcer then if cfxReconMode.announcer then
trigger.action.outTextForCoalition(mySide, theScout:getName() .. " reports new ground contact " .. theGroup:getName(), 30) local msg = cfxReconMode.generateSALT(theScout, theGroup)
trigger.action.outText("+++recon: announced for side " .. mySide, 30) trigger.action.outTextForCoalition(mySide, msg, 30)
-- trigger.action.outTextForCoalition(mySide, theScout:getName() .. " reports new ground contact " .. theGroup:getName(), 30)
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: announced for side " .. mySide, 30)
end
-- play a sound -- play a sound
trigger.action.outSoundForCoalition(mySide, cfxReconMode.reconSound) trigger.action.outSoundForCoalition(mySide, cfxReconMode.reconSound)
else else
--trigger.action.outText("+++recon: announcer off", 30)
end end
-- see if it was a prio target -- see if it was a prio target
if cfxReconMode.isStringInList(theGroup:getName(), cfxReconMode.prioList) then local inList, gName = cfxReconMode.isStringInList(theGroup:getName(), cfxReconMode.prioList)
if cfxReconMode.announcer then if inList then
trigger.action.outTextForCoalition(mySide, "Priority target confirmed", 30) -- if cfxReconMode.announcer then
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: Priority target spotted", 30)
end end
-- invoke callbacks -- invoke callbacks
cfxReconMode.invokeCallbacks("priotity", mySide, theScout, theGroup, theGroup:getName()) cfxReconMode.invokeCallbacks("priority", mySide, theScout, theGroup, theGroup:getName())
-- increase prio flag -- increase prio flag
if cfxReconMode.prioFlag then if cfxReconMode.prioFlag then
local currVal = trigger.misc.getUserFlag(cfxReconMode.prioFlag) cfxZones.pollFlag(cfxReconMode.prioFlag, cfxReconMode.method, cfxReconMode.theZone)
trigger.action.setUserFlag(cfxReconMode.prioFlag, currVal + 1) end
-- see if we were passed additional info in zInfo
if gName and cfxReconMode.zoneInfo[gName] then
local zInfo = cfxReconMode.zoneInfo[gName]
if zInfo.prioMessage then
-- prio message displays even when announcer is off
local msg = zInfo.prioMessage
msg = cfxReconMode.processZoneMessage(msg, zInfo.theZone)
trigger.action.outTextForCoalition(mySide, msg, 30)
if cfxReconMode.verbose or zInfo.theZone.verbose then
trigger.action.outText("+++rcn: prio message sent for prio target zone <" .. zInfo.theZone.name .. ">",30)
end
end
if zInfo.theFlag then
cfxZones.pollFlag(zInfo.theFlag, cfxReconMode.method, zInfo.theZone)
if cfxReconMode.verbose or zInfo.theZone.verbose then
trigger.action.outText("+++rcn: banging <" .. zInfo.theFlag .. "> for prio target zone <" .. zInfo.theZone.name .. ">",30)
end
end
end end
else else
-- invoke callbacks -- invoke callbacks
@ -339,8 +549,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
-- increase normal flag -- increase normal flag
if cfxReconMode.detectFlag then if cfxReconMode.detectFlag then
local currVal = trigger.misc.getUserFlag(cfxReconMode.detectFlag) cfxZones.pollFlag(cfxReconMode.detectFlag, cfxReconMode.method, cfxReconMode.theZone)
trigger.action.setUserFlag(cfxReconMode.detectFlag, currVal + 1)
end end
end end
end end
@ -371,7 +580,8 @@ function cfxReconMode.performReconForUnit(theScout)
local groupName = theGroup:getName() local groupName = theGroup:getName()
if cfxReconMode.detectedGroups[groupName] == nil then if cfxReconMode.detectedGroups[groupName] == nil then
-- only now check against blackList -- only now check against blackList
if not cfxReconMode.isStringInList(groupName, cfxReconMode.blackList) then local inList, gName = cfxReconMode.isStringInList(groupName, cfxReconMode.blackList)
if not inList then
-- visible and not yet seen -- visible and not yet seen
-- perhaps add some percent chance now -- perhaps add some percent chance now
-- remember that we know this group -- remember that we know this group
@ -464,11 +674,62 @@ function cfxReconMode.autoRemove()
end end
end end
-- late eval player
function cfxReconMode.lateEvalPlayerUnit(theUnit)
-- check if a player is inside one of the scout zones
-- first: quick check if the player is already in a list
local aGroup = theUnit:getGroup()
local gName = aGroup:getName()
if cfxReconMode.allowedScouts[gName] then return end
if cfxReconMode.blindScouts[gName] then return end
-- get location
local p = theUnit:getPoint()
-- iterate all scoutZones
for idx, theZone in pairs (cfxReconMode.scoutZones) do
local isScout = theZone.isScout
local dynamic = theZone.dynamic
local inZone = cfxZones.pointInZone(p, theZone)
if inZone then
if isScout then
cfxReconMode.addToAllowedScoutList(aGroup, dynamic)
if cfxReconMode.verbose or theZone.verbose then
if dynamic then
trigger.action.outText("+++rcn: added LATE DYNAMIC PLAYER" .. gName .. " to allowed scouts", 30)
else
trigger.action.outText("+++rcn: added LATE PLAYER " .. gName .. " to allowed scouts", 30)
end
end
else
cfxReconMode.addToBlindScoutList(aGroup, dynamic)
if cfxReconMode.verbose or theZone.verbose then
if dynamic then
trigger.action.outText("+++rcn: added LATE DYNAMIC PLAYER" .. gName .. " to BLIND scouts list", 30)
else
trigger.action.outText("+++rcn: added LATE PLAYER " .. gName .. " to BLIND scouts list", 30)
end
end
end
return -- we stop after first found
end
end
end
-- event handler -- event handler
function cfxReconMode:onEvent(event) function cfxReconMode:onEvent(event)
if not event then return end if not event then return end
if not event.initiator then return end if not event.initiator then return end
if not (event.id == 15 or event.id == 3) then return end
local theUnit = event.initiator local theUnit = event.initiator
if not theUnit:isExist() then return end
local theGroup = theUnit:getGroup()
-- trigger.action.outText("+++rcn-ENTER onEvent: " .. event.id .. " for <" .. theUnit:getName() .. ">", 30)
if not theGroup then return end
local gCat = theGroup:getCategory()
-- only continue if cat = 0 (aircraft) or 1 (helo)
if gCat > 1 then return end
-- we simply add scouts as they are garbage-collected -- we simply add scouts as they are garbage-collected
-- every so often when they do not exist -- every so often when they do not exist
@ -481,26 +742,71 @@ function cfxReconMode:onEvent(event)
-- scout when they are on that side. in that case -- scout when they are on that side. in that case
-- you must add manually -- you must add manually
local theSide = theUnit:getCoalition() local theSide = theUnit:getCoalition()
if theSide == 0 and not cfxReconMode.greyScouts then
return -- grey scouts are not allowed local isPlayer = theUnit:getPlayerName()
end if isPlayer then
if theSide == 1 and not cfxReconMode.redScouts then -- since players wake up late, we lazy-eval their group
return -- grey scouts are not allowed -- and add it to the blind/scout lists
end cfxReconMode.lateEvalPlayerUnit(theUnit)
if theSide == 2 and not cfxReconMode.blueScouts then if cfxReconMode.verbose then
return -- grey scouts are not allowed trigger.action.outText("+++rcn: late player check complete for <" .. theUnit:getName() .. ">", 30)
end
else
isPlayer = false -- safer than sorry
end end
if cfxReconMode.playerOnlyRecon then if cfxReconMode.autoRecon then
if not theUnit:getPlayerName() then if theSide == 0 and not cfxReconMode.greyScouts then
return -- only players can do recon. this unit is AI return -- grey scouts are not allowed
end
if theSide == 1 and not cfxReconMode.redScouts then
return -- grey scouts are not allowed
end
if theSide == 2 and not cfxReconMode.blueScouts then
return -- grey scouts are not allowed
end
if cfxReconMode.playerOnlyRecon then
if not isPlayer then
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: <" .. theUnit:getName() .. "> filtered: no player unit", 30)
end
return -- only players can do recon. this unit is AI
end
end end
end end
-- check if cfxReconMode.autoRecon is enabled
-- otherwise, abort the aircraft is not in
-- scourlist
local gName = theGroup:getName()
if not cfxReconMode.autoRecon then
-- no auto-recon. plane must be in scouts list
local inList, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.allowedScouts)
if not inList then
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: <" .. theUnit:getName() .. "> filtered: not in scout list", 30)
end
return
end
end
-- check if aircraft is in blindlist
-- abort if so
local inList, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.blindScouts)
if inList then
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: <" .. theUnit:getName() .. "> filtered: unit cannot scout", 30)
end
return
end
if cfxReconMode.verbose then if cfxReconMode.verbose then
trigger.action.outText("+++rcn: event " .. event.id .. " for unit " .. theUnit:getName(), 30) trigger.action.outText("+++rcn: event " .. event.id .. " for unit " .. theUnit:getName(), 30)
end end
cfxReconMode.addScout(theUnit) cfxReconMode.addScout(theUnit)
end end
-- trigger.action.outText("+++rcn-onEvent: " .. event.id .. " for <" .. theUnit:getName() .. ">", 30)
end end
-- --
@ -512,14 +818,49 @@ function cfxReconMode.processScoutGroups(theGroups)
-- we are very early in the mission, only few groups really -- we are very early in the mission, only few groups really
-- exist now, the rest of the units come in with 15 event -- exist now, the rest of the units come in with 15 event
if aGroup:isExist() then if aGroup:isExist() then
local allUnits = Group.getUnits(aGroup) -- see if we want to add these aircraft to the
for idy, aUnit in pairs (allUnits) do -- active scout list
if aUnit:isExist() then
cfxReconMode.addScout(aUnit) local gName = aGroup:getName()
if cfxReconMode.verbose then local isBlind, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.blindScouts)
trigger.action.outText("+++rcn: added unit " ..aUnit:getName() .. " to pool at startup", 30) local isScout, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.allowedScouts)
local doAdd = cfxReconMode.autoRecon
if cfxReconMode.autoRecon then
local theSide = aGroup:getCoalition()
if theSide == 0 and not cfxReconMode.greyScouts then
doAdd = false
elseif theSide == 1 and not cfxReconMode.redScouts then
doAdd = false
elseif theSide == 2 and not cfxReconMode.blueScouts then
doAdd = false
end
end
if isBlind then doAdd = false end
if isScout then doAdd = true end -- overrides all
if doAdd then
local allUnits = Group.getUnits(aGroup)
for idy, aUnit in pairs (allUnits) do
if aUnit:isExist() then
if cfxReconMode.autoRecon and cfxReconMode.playerOnlyRecon and (aUnit:getPlayerName() == nil)
then
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: skipped unit " ..aUnit:getName() .. " because not player unit", 30)
end
else
cfxReconMode.addScout(aUnit)
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: added unit " ..aUnit:getName() .. " to pool at startup", 30)
end
end
end end
end end
else
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: filtered group " .. gName .. " from being entered into scout pool at startup", 30)
end
end end
end end
end end
@ -527,20 +868,23 @@ end
function cfxReconMode.initScouts() function cfxReconMode.initScouts()
-- get all groups of aircraft. Unrolled loop 0..2 -- get all groups of aircraft. Unrolled loop 0..2
-- added helicopters, removed check for grey/red/bluescouts,
-- as that happens in processScoutGroups
local theAirGroups = {} local theAirGroups = {}
if cfxReconMode.greyScouts then theAirGroups = coalition.getGroups(0, 0) -- 0 = aircraft
theAirGroups = coalition.getGroups(0, 0) -- 0 = aircraft cfxReconMode.processScoutGroups(theAirGroups)
cfxReconMode.processScoutGroups(theAirGroups) theAirGroups = coalition.getGroups(0, 1) -- 1 = helicopter
end cfxReconMode.processScoutGroups(theAirGroups)
if cfxReconMode.redScouts then
theAirGroups = coalition.getGroups(1, 0) -- 1 = red, 0 = aircraft
cfxReconMode.processScoutGroups(theAirGroups)
end
if cfxReconMode.blueScouts then theAirGroups = coalition.getGroups(1, 0) -- 0 = aircraft
theAirGroups = coalition.getGroups(2, 0) -- 2 = blue, 0 = aircraft cfxReconMode.processScoutGroups(theAirGroups)
cfxReconMode.processScoutGroups(theAirGroups) theAirGroups = coalition.getGroups(1, 1) -- 1 = helicopter
end cfxReconMode.processScoutGroups(theAirGroups)
theAirGroups = coalition.getGroups(2, 0) -- 0 = aircraft
cfxReconMode.processScoutGroups(theAirGroups)
theAirGroups = coalition.getGroups(2, 1) -- 1 = helicopter
cfxReconMode.processScoutGroups(theAirGroups)
end end
-- --
@ -575,12 +919,20 @@ function cfxReconMode.readConfigZone()
if cfxZones.hasProperty(theZone, "prio+") then if cfxZones.hasProperty(theZone, "prio+") then
cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio+", "none") cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio+", "none")
elseif cfxZones.hasProperty(theZone, "prio!") then
cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio!", "*<none>")
end end
if cfxZones.hasProperty(theZone, "detect+") then if cfxZones.hasProperty(theZone, "detect+") then
cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect+", "none") cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect+", "none")
elseif cfxZones.hasProperty(theZone, "detect!") then
cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect!", "*<none>")
end end
cfxReconMode.method = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
if cfxZones.hasProperty(theZone, "reconMethod") then
cfxReconMode.method = cfxZones.getStringFromZoneProperty(theZone, "reconMethod", "inc")
end
cfxReconMode.applyMarks = cfxZones.getBoolFromZoneProperty(theZone, "applyMarks", true) cfxReconMode.applyMarks = cfxZones.getBoolFromZoneProperty(theZone, "applyMarks", true)
cfxReconMode.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true) cfxReconMode.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
@ -591,6 +943,101 @@ function cfxReconMode.readConfigZone()
cfxReconMode.removeWhenDestroyed = cfxZones.getBoolFromZoneProperty(theZone, "autoRemove", true) cfxReconMode.removeWhenDestroyed = cfxZones.getBoolFromZoneProperty(theZone, "autoRemove", true)
cfxReconMode.mgrs = cfxZones.getBoolFromZoneProperty(theZone, "mgrs", false)
cfxReconMode.theZone = theZone -- save this zone
end
--
-- read blackList and prio list groups
--
function cfxReconMode.processReconZone(theZone)
local theList = cfxZones.getStringFromZoneProperty(theZone, "recon", "prio")
theList = string.upper(theList)
local isBlack = dcsCommon.stringStartsWith(theList, "BLACK")
local zInfo = {}
zInfo.theZone = theZone
zInfo.isBlack = isBlack
if cfxZones.hasProperty(theZone, "spotted!") then
zInfo.theFlag = cfxZones.getStringFromZoneProperty(theZone, "spotted!", "*<none>")
end
if cfxZones.hasProperty(theZone, "prioMessage") then
zInfo.prioMessage = cfxZones.getStringFromZoneProperty(theZone, "prioMessage", "<none>")
end
local dynamic = cfxZones.getBoolFromZoneProperty(theZone, "dynamic", false)
zInfo.dynamic = dynamic
local categ = 2 -- ground troops only
local allGroups = cfxZones.allGroupsInZone(theZone, categ)
for idx, aGroup in pairs(allGroups) do
local gName = aGroup:getName()
cfxReconMode.zoneInfo[gName] = zInfo
if isBlack then
cfxReconMode.addToBlackList(aGroup, dynamic)
if cfxReconMode.verbose or theZone.verbose then
if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to blacklist", 30)
else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to blacklist", 30)
end
end
else
cfxReconMode.addToPrioList(aGroup, dynamic)
if cfxReconMode.verbose or theZone.verbose then
if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to priority target list", 30)
else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to priority target list", 30)
end
end
end
end
end
function cfxReconMode.processScoutZone(theZone)
local isScout = cfxZones.getBoolFromZoneProperty(theZone, "scout", true)
local dynamic = cfxZones.getBoolFromZoneProperty(theZone, "dynamic")
theZone.dynamic = dynamic
theZone.isScout = isScout
local categ = 0 -- aircraft
local allFixed = cfxZones.allGroupsInZone(theZone, categ)
local categ = 1 -- helos
local allRotor = cfxZones.allGroupsInZone(theZone, categ)
local allGroups = dcsCommon.combineTables(allFixed, allRotor)
for idx, aGroup in pairs(allGroups) do
if isScout then
cfxReconMode.addToAllowedScoutList(aGroup, dynamic)
if cfxReconMode.verbose or theZone.verbose then
if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to allowed scouts", 30)
else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to allowed scouts", 30)
end
end
else
cfxReconMode.addToBlindScoutList(aGroup, dynamic)
if cfxReconMode.verbose or theZone.verbose then
if dynamic then trigger.action.outText("+++rcn: added DYNAMIC " .. aGroup:getName() .. " to BLIND scouts list", 30)
else trigger.action.outText("+++rcn: added " .. aGroup:getName() .. " to BLIND scouts list", 30)
end
end
end
end
table.insert(cfxReconMode.scoutZones, theZone)
end
function cfxReconMode.readReconGroups()
local attrZones = cfxZones.getZonesWithAttributeNamed("recon")
for k, aZone in pairs(attrZones) do
cfxReconMode.processReconZone(aZone)
end
end
function cfxReconMode.readScoutGroups()
local attrZones = cfxZones.getZonesWithAttributeNamed("scout")
for k, aZone in pairs(attrZones) do
cfxReconMode.processScoutZone(aZone)
end
end end
-- --
@ -606,6 +1053,12 @@ function cfxReconMode.start()
-- read config -- read config
cfxReconMode.readConfigZone() cfxReconMode.readConfigZone()
-- gather prio and blacklist groups
cfxReconMode.readReconGroups()
-- gather allowed and forbidden scouts
cfxReconMode.readScoutGroups()
-- gather exiting planes -- gather exiting planes
cfxReconMode.initScouts() cfxReconMode.initScouts()
@ -618,7 +1071,7 @@ function cfxReconMode.start()
cfxReconMode.autoRemove() cfxReconMode.autoRemove()
end end
if cfxReconMode.autoRecon then if true or cfxReconMode.autoRecon then
-- install own event handler to detect -- install own event handler to detect
-- when a unit takes off and add it to scout -- when a unit takes off and add it to scout
-- roster -- roster
@ -651,8 +1104,9 @@ ideas:
- renew lease. when already sighted, simply renew lease, maybe update location. - renew lease. when already sighted, simply renew lease, maybe update location.
- update marks and renew lease - update marks and renew lease
TODO: red+ and blue+ - flags to increase when a plane of the other side is detected TODO: red+ and blue+ - flags to increase when a plane of the other side is detected
TODO: recon: scout and blind for aircraft in group to add / remove scouts, maybe use scout keyword
allow special bangs per priority group
--]]-- --]]--

View File

@ -1516,7 +1516,7 @@ function cfxZones.testZoneFlag(theZone, theFlagName, theMethod, latchName)
-- get last value from latch -- get last value from latch
local lastVal = theZone[latchName] local lastVal = theZone[latchName]
if not lastVal then if not lastVal then
trigger.action.outText("+++Zne: latch <" .. latchName .. "> not valid for zone " .. theZone.name, 30) trigger.action.outText("+++Zne: latch <" .. latchName .. "> not valid for zone " .. theZone.name, 30) -- intentional break here
return nil, nil return nil, nil
end end

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "2.6.4" dcsCommon.version = "2.6.5"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB 2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB - clockPositionOfARelativeToB
@ -74,6 +74,9 @@ dcsCommon.version = "2.6.4"
2.6.2 - new combineTables() 2.6.2 - new combineTables()
2.6.3 - new tacan2freq() 2.6.3 - new tacan2freq()
2.6.4 - new processHMS() 2.6.4 - new processHMS()
2.6.5 - new bearing2compass()
- new bearingdegrees2compass()
- new latLon2Text() - based on mist
--]]-- --]]--
@ -530,6 +533,25 @@ dcsCommon.version = "2.6.4"
return "North" return "North"
end end
function dcsCommon.bearing2compass(inrad)
local bearing = math.floor(inrad / math.pi * 180)
if bearing < 0 then bearing = bearing + 360 end
if bearing > 360 then bearing = bearing - 360 end
return dcsCommon.bearingdegrees2compass(bearing)
end
function dcsCommon.bearingdegrees2compass(bearing)
if bearing < 23 then return "North" end
if bearing < 68 then return "NE" end
if bearing < 112 then return "East" end
if bearing < 158 then return "SE" end
if bearing < 202 then return "South" end
if bearing < 248 then return "SW" end
if bearing < 292 then return "West" end
if bearing < 338 then return "NW" end
return "North"
end
function dcsCommon.clockPositionOfARelativeToB(A, B, headingOfBInDegrees) function dcsCommon.clockPositionOfARelativeToB(A, B, headingOfBInDegrees)
-- o'clock notation -- o'clock notation
if not A then return "***error:A***" end if not A then return "***error:A***" end
@ -2212,7 +2234,7 @@ function dcsCommon.getUnitHeading(theUnit)
local pos = theUnit:getPosition() -- returns three vectors, p is location local pos = theUnit:getPosition() -- returns three vectors, p is location
local heading = math.atan2(pos.x.z, pos.x.x) local heading = math.atan2(pos.x.z, pos.x.x)
-- make sure positive only, add 260 degrees -- make sure positive only, add 360 degrees
if heading < 0 then if heading < 0 then
heading = heading + 2 * math.pi -- put heading in range of 0 to 2*pi heading = heading + 2 * math.pi -- put heading in range of 0 to 2*pi
end end
@ -2255,6 +2277,50 @@ function dcsCommon.coalition2county(inCoalition)
end end
function dcsCommon.latLon2Text(lat, lon)
-- inspired by mist, thanks Grimes!
-- returns two strings: lat and lon
-- determine hemispheres by sign
local latHemi, lonHemi
if lat > 0 then latHemi = 'N' else latHemi = 'S' end
if lon > 0 then lonHemi = 'E' else lonHemi = 'W' end
-- remove sign since we have hemi
lat = math.abs(lat)
lon = math.abs(lon)
-- calc deg / mins
local latDeg = math.floor(lat)
local latMin = (lat - latDeg) * 60
local lonDeg = math.floor(lon)
local lonMin = (lon - lonDeg) * 60
-- calc seconds
local rawLatMin = latMin
latMin = math.floor(latMin)
local latSec = (rawLatMin - latMin) * 60
local rawLonMin = lonMin
lonMin = math.floor(lonMin)
local lonSec = (rawLonMin - lonMin) * 60
-- correct for rounding errors
if latSec >= 60 then
latSec = latSec - 60
latMin = latMin + 1
end
if lonSec >= 60 then
lonSec = lonSec - 60
lonMin = lonMin + 1
end
-- prepare string output
local secFrmtStr = '%06.3f'
local lat = string.format('%02d', latDeg) .. '°' .. string.format('%02d', latMin) .. "'" .. string.format(secFrmtStr, latSec) .. '"' .. latHemi
local lon = string.format('%02d', lonDeg) .. '°' .. string.format('%02d', lonMin) .. "'" .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi
return lat, lon
end
-- --
-- --
-- INIT -- INIT

View File

@ -1,5 +1,5 @@
guardianAngel = {} guardianAngel = {}
guardianAngel.version = "2.0.3" guardianAngel.version = "3.0.0"
guardianAngel.ups = 10 guardianAngel.ups = 10
guardianAngel.launchWarning = true -- detect launches and warn pilot guardianAngel.launchWarning = true -- detect launches and warn pilot
guardianAngel.intervention = true -- remove missiles just before hitting guardianAngel.intervention = true -- remove missiles just before hitting
@ -9,6 +9,10 @@ guardianAngel.announcer = true -- angel talks to you
guardianAngel.private = false -- angel only talks to group guardianAngel.private = false -- angel only talks to group
guardianAngel.autoAddPlayers = true guardianAngel.autoAddPlayers = true
guardianAngel.active = true -- can be turned on / off
guardianAngel.angelicZones = {}
guardianAngel.requiredLibs = { guardianAngel.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
"cfxZones", -- Zones, of course "cfxZones", -- Zones, of course
@ -37,6 +41,18 @@ guardianAngel.requiredLibs = {
- can be dangerous - can be dangerous
2.0.3 - fxDistance 2.0.3 - fxDistance
- mea cupa capability - 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
This script detects missiles launched against protected aircraft an This script detects missiles launched against protected aircraft an
@ -51,7 +67,7 @@ guardianAngel.safetyFactor = 1.8 -- for calculating dealloc range
guardianAngel.unitsToWatchOver = {} -- I'll watch over these guardianAngel.unitsToWatchOver = {} -- I'll watch over these
guardianAngel.missilesInTheAir = {} -- missiles in the air guardianAngel.missilesInTheAir = {} -- missiles in the air
guardianAngel.missilesAndTargets = {} -- permanent log which missile was aimed at whom
guardianAngel.callBacks = {} -- callbacks guardianAngel.callBacks = {} -- callbacks
-- callback signature: callBack(reason, targetUnitName, weaponName) -- callback signature: callBack(reason, targetUnitName, weaponName)
-- reasons (string): "launch", "miss", "reacquire", "trackloss", "disappear", "intervention" -- reasons (string): "launch", "miss", "reacquire", "trackloss", "disappear", "intervention"
@ -77,9 +93,14 @@ function guardianAngel.addUnitToWatch(aUnit)
end end
if not aUnit then return end if not aUnit then return end
local unitName = aUnit:getName() local unitName = aUnit:getName()
local isNew = guardianAngel.unitsToWatchOver[unitName] == nil
guardianAngel.unitsToWatchOver[unitName] = aUnit guardianAngel.unitsToWatchOver[unitName] = aUnit
if guardianAngel.verbose then if guardianAngel.verbose then
trigger.action.outText("+++gA: now watching unit " .. aUnit:getName(), 30) if isNew then
trigger.action.outText("+++gA: now watching unit " .. unitName, 30)
else
trigger.action.outText("+++gA: updating unit " .. unitName, 30)
end
end end
end end
@ -104,14 +125,17 @@ end
-- --
-- watch q items -- watch q items
-- --
function guardianAngel.createQItem(theWeapon, theTarget, detectProbability) function guardianAngel.createQItem(theWeapon, theTarget, threat)
if not theWeapon then return nil end if not theWeapon then return nil end
if not theTarget then return nil end if not theTarget then return nil end
if not theTarget:isExist() then return nil end if not theTarget:isExist() then return nil end
if not detectProbability then detectProbability = 1.0 end 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 theItem = {}
theItem.theWeapon = theWeapon theItem.theWeapon = theWeapon -- weapon that we are tracking
theItem.wP = theWeapon:getPoint() -- save location --theItem.wP = theWeapon:getPoint() -- save location
theItem.weaponName = theWeapon:getName() theItem.weaponName = theWeapon:getName()
theItem.theTarget = theTarget theItem.theTarget = theTarget
theItem.tGroup = theTarget:getGroup() theItem.tGroup = theTarget:getGroup()
@ -119,14 +143,63 @@ function guardianAngel.createQItem(theWeapon, theTarget, detectProbability)
theItem.targetName = theTarget:getName() theItem.targetName = theTarget:getName()
theItem.launchTimeStamp = timer.getTime() theItem.launchTimeStamp = timer.getTime()
theItem.lastCheckTimeStamp = -1000 --theItem.lastCheckTimeStamp = -1000
theItem.lastDistance = math.huge theItem.lastDistance = math.huge
theItem.detected = false theItem.detected = false
theItem.lostTrack = false -- so we can detect sneakies! --theItem.lostTrack = false -- so we can detect sneakies!
theItem.missed = false -- just keep watching for re-ack theItem.missed = false -- just keep watching for re-ack
theItem.threat = threat
theItem.lastDesc = "(new)"
theItem.timeStamp = timer.getTime()
return theItem return theItem
end end
function guardianAngel.retargetItem(theItem, theTarget, threat)
theItem.theTarget = nil -- may cause trouble
if not theTarget or not theTarget:isExist() then
theItem.threat = false
theItem.timeStamp = timer.getTime()
theItem.target = nil
theItem.targetName = "(substitute)"
theItem.lastDistance = math.huge
-- theItem.lostTrack = false
theItem.missed = false
theItem.lastDesc = "(retarget)"
return
end
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()
theItem.tID = theItem.tGroup:getID()
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
end
return nil
end
-- calculate a point in direction from plane (pln) to weapon (wpn), dist meters -- calculate a point in direction from plane (pln) to weapon (wpn), dist meters
function guardianAngel.calcSafeExplosionPoint(wpn, pln, dist) function guardianAngel.calcSafeExplosionPoint(wpn, pln, dist)
@ -138,6 +211,7 @@ function guardianAngel.calcSafeExplosionPoint(wpn, pln, dist)
return newPoint return newPoint
end end
--[[--
function guardianAngel.bubbleCheck(wPos, w) function guardianAngel.bubbleCheck(wPos, w)
if true then return false end if true then return false end
for idx, aProtectee in pairs (guardianAngel.unitsToWatchOver) do for idx, aProtectee in pairs (guardianAngel.unitsToWatchOver) do
@ -154,41 +228,102 @@ function guardianAngel.bubbleCheck(wPos, w)
end end
return false return false
end end
--]]--
function guardianAngel.monitorItem(theItem) function guardianAngel.monitorItem(theItem)
local w = theItem.theWeapon local w = theItem.theWeapon
local ID = theItem.tID local ID = theItem.tID
if not w then return false end if not w then return false end
if not w:isExist() then if not w:isExist() then
if (not theItem.missed) and (not theItem.lostTrack) then --if (not theItem.missed) and (not theItem.lostTrack) then
local desc = theItem.weaponName .. ": DISAPPEARED" local desc = theItem.weaponName .. ": DISAPPEARED"
if guardianAngel.announcer then if guardianAngel.announcer and theItem.threat then
if guardianAngel.private then if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, 30) trigger.action.outTextForGroup(ID, desc, 30)
else else
trigger.action.outText(desc, 30) trigger.action.outText(desc, 30)
end end
end end
if guardianAngel.verbose then
trigger.action.outText("+++gA: missile disappeared: <" .. theItem.weaponName .. ">, aimed at <" .. theItem.targetName .. ">",30)
end
guardianAngel.invokeCallbacks("disappear", theItem.targetName, theItem.weaponName) guardianAngel.invokeCallbacks("disappear", theItem.targetName, theItem.weaponName)
end -- end
return false return false
end end
local t = theItem.theTarget local t = theItem.theTarget
local currentTarget = w:getTarget() local currentTarget = w:getTarget()
local oldWPos = theItem.wP
-- 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 then
-- get current name to check against last target name
ctName = currentTarget:getName()
else
-- currentTarget has disappeared, kill the 'threat flag'
-- theItem.threat = false
ctName = "***guardianangel.not.set"
end
if ctName and ctName ~= theItem.targetName then
if guardianAngel.verbose then
--trigger.action.outText("+++gA: RETARGETING for <" .. theItem.weaponName .. ">: from <" .. theItem.targetName .. "> to <" .. ctName .. ">", 30)
end
-- see if it's a threat to us now
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
isThreat = true
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 then
trigger.action.outTextForGroup(ID, desc, 30)
else
trigger.action.outText(desc, 30)
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 local A = w:getPoint() -- A is new point of weapon
theItem.wp = A -- update new position, old is in oldWPos -- theItem.wp = A -- update new position, old is in oldWPos
-- new code: safety check with ALL protected wings -- new code: safety check with ALL protected wings
local bubbleThreat = guardianAngel.bubbleCheck(A, w) -- local bubbleThreat = guardianAngel.bubbleCheck(A, w)
-- safety check removed, no benefit after new code
local B local B
if currentTarget then B = currentTarget:getPoint() else B = A end if currentTarget then B = currentTarget:getPoint() else B = A end
local d = math.floor(dcsCommon.dist(A, B)) local d = math.floor(dcsCommon.dist(A, B))
theItem.lastDistance = d -- save it for post mortem
local desc = theItem.weaponName .. ": " local desc = theItem.weaponName .. ": "
if t == currentTarget then if true or t == currentTarget then
desc = desc .. "tracking " .. theItem.targetName .. ", d = " .. d .. "m" desc = desc .. "tracking " .. theItem.targetName .. ", d = " .. d .. "m"
local vcc = dcsCommon.getClosingVelocity(t, w) local vcc = dcsCommon.getClosingVelocity(t, w)
desc = desc .. ", Vcc = " .. math.floor(vcc) .. "m/s" desc = desc .. ", Vcc = " .. math.floor(vcc) .. "m/s"
@ -200,13 +335,15 @@ function guardianAngel.monitorItem(theItem)
-- destroy the missile -- destroy the missile
local lethalRange = math.abs(vcc / guardianAngel.ups) * guardianAngel.safetyFactor local lethalRange = math.abs(vcc / guardianAngel.ups) * guardianAngel.safetyFactor
desc = desc .. ", LR= " .. math.floor(lethalRange) .. "m" desc = desc .. ", LR= " .. math.floor(lethalRange) .. "m"
theItem.lastDesc = desc
theItem.timeStamp = timer.getTime()
if guardianAngel.intervention and if guardianAngel.intervention and
d <= lethalRange + 10 d <= lethalRange + 10
then then
desc = desc .. " ANGEL INTERVENTION" desc = desc .. " ANGEL INTERVENTION"
if theItem.lostTrack then desc = desc .. " (little sneak!)" end --if theItem.lostTrack then desc = desc .. " (little sneak!)" end
if theItem.missed then desc = desc .. " (missed you!)" end --if theItem.missed then desc = desc .. " (missed you!)" end
if guardianAngel.announcer then if guardianAngel.announcer then
if guardianAngel.private then if guardianAngel.private then
@ -232,8 +369,8 @@ function guardianAngel.monitorItem(theItem)
d <= guardianAngel.minMissileDist -- god's override d <= guardianAngel.minMissileDist -- god's override
then then
desc = desc .. " GOD INTERVENTION" desc = desc .. " GOD INTERVENTION"
if theItem.lostTrack then desc = desc .. " (little sneak!)" end --if theItem.lostTrack then desc = desc .. " (little sneak!)" end
if theItem.missed then desc = desc .. " (missed you!)" end --if theItem.missed then desc = desc .. " (missed you!)" end
if guardianAngel.announcer then if guardianAngel.announcer then
if guardianAngel.private then if guardianAngel.private then
@ -251,6 +388,7 @@ function guardianAngel.monitorItem(theItem)
return false -- remove from list return false -- remove from list
end end
else else
--[[--
if not theItem.lostTrack then if not theItem.lostTrack then
desc = desc .. "Missile LOST TRACK" desc = desc .. "Missile LOST TRACK"
@ -264,10 +402,12 @@ function guardianAngel.monitorItem(theItem)
guardianAngel.invokeCallbacks("trackloss", theItem.targetName, theItem.weaponName) guardianAngel.invokeCallbacks("trackloss", theItem.targetName, theItem.weaponName)
theItem.lostTrack = true theItem.lostTrack = true
end end
theItem.lastDistance = d --]]--
return true -- true because they can re-acquire! -- theItem.lastDistance = d
-- return true -- true because they can re-acquire!
end end
--[[--
if d > theItem.lastDistance then if d > theItem.lastDistance then
-- this can be wrong because if a missile is launched -- this can be wrong because if a missile is launched
-- at an angle, it can initially look as if it missed -- at an angle, it can initially look as if it missed
@ -287,7 +427,8 @@ function guardianAngel.monitorItem(theItem)
theItem.lastDistance = d theItem.lastDistance = d
return true -- better not disregard - they can re-acquire! return true -- better not disregard - they can re-acquire!
end end
--]]--
--[[--
if theItem.missed and d < theItem.lastDistance then if theItem.missed and d < theItem.lastDistance then
desc = desc .. " Missile RE-ACQUIRED!" desc = desc .. " Missile RE-ACQUIRED!"
@ -301,8 +442,9 @@ function guardianAngel.monitorItem(theItem)
theItem.missed = false theItem.missed = false
guardianAngel.invokeCallbacks("reacquire", theItem.targetName, theItem.weaponName) guardianAngel.invokeCallbacks("reacquire", theItem.targetName, theItem.weaponName)
end end
--]]--
theItem.lastDistance = d -- theItem.lastDistance = d
return true return true
end end
@ -316,7 +458,7 @@ function guardianAngel.monitorMissiles()
-- guardianAngel.detectItem(anItem) -- guardianAngel.detectItem(anItem)
-- see if the weapon is still in existence -- see if the weapon is still in existence
stillAlive = guardianAngel.monitorItem(anItem) local stillAlive = guardianAngel.monitorItem(anItem)
if stillAlive then if stillAlive then
table.insert(newArray, anItem) table.insert(newArray, anItem)
end end
@ -324,6 +466,31 @@ function guardianAngel.monitorMissiles()
guardianAngel.missilesInTheAir = newArray guardianAngel.missilesInTheAir = newArray
end end
function guardianAngel.filterItem(theItem)
local w = theItem.theWeapon
if not w 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
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
end
guardianAngel.missilesInTheAir = newArray
end
-- --
-- E V E N T P R O C E S S I N G -- E V E N T P R O C E S S I N G
-- --
@ -348,22 +515,79 @@ function guardianAngel.postProcessor(event)
-- don't do anything for now -- don't do anything for now
end end
function guardianAngel.getAngelicZoneForUnit(theUnit)
for idx, theZone in pairs(guardianAngel.angelicZones) do
if cfxZones.unitInZone(theUnit, theZone) then
return theZone
end
end
return nil
end
-- event callback from dcsCommon event handler. preProcessor has returned true -- event callback from dcsCommon event handler. preProcessor has returned true
function guardianAngel.somethingHappened(event) function guardianAngel.somethingHappened(event)
-- when this is invoked, the preprocessor guarantees that -- when this is invoked, the preprocessor guarantees that
-- it's an interesting event and has initiator -- it's an interesting event and has initiator
local ID = event.id local ID = event.id
local theUnit = event.initiator local theUnit = event.initiator
local playerName = theUnit:getPlayerName() -- nil if not a player -- 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
local mustProtect = false
if ID == 15 and playerName then if ID == 15 and playerName then
-- this is a player created unit -- this is a player created unit
if guardianAngel.verbose then if guardianAngel.verbose then
trigger.action.outText("+++gA: unit born " .. theUnit:getName(), 30) trigger.action.outText("+++gA: player unit born " .. theUnit:getName(), 30)
end end
if guardianAngel.autoAddPlayers then 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) guardianAngel.addUnitToWatch(theUnit)
end end
return
elseif ID == 15 then
-- AI spawn. check if it is an aircraft and in an angelic zone
-- docs say that initiator is object. so let's see if when we
-- 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
local theGroup = theUnit:getGroup()
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 return
end end
@ -372,9 +596,23 @@ function guardianAngel.somethingHappened(event)
if guardianAngel.verbose then if guardianAngel.verbose then
trigger.action.outText("+++gA: player seated in unit " .. theUnit:getName(), 30) trigger.action.outText("+++gA: player seated in unit " .. theUnit:getName(), 30)
end end
if guardianAngel.autoAddPlayers then 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) guardianAngel.addUnitToWatch(theUnit)
end end
return return
end end
@ -390,6 +628,7 @@ function guardianAngel.somethingHappened(event)
if ID == 1 then if ID == 1 then
-- even if not active, we collect missile data
-- someone shot something. see if it is fire directed at me -- someone shot something. see if it is fire directed at me
local theWeapon = event.weapon local theWeapon = event.weapon
local theTarget local theTarget
@ -406,11 +645,22 @@ function guardianAngel.somethingHappened(event)
-- if we get here, we have weapon aimed at a target -- if we get here, we have weapon aimed at a target
local targetName = theTarget:getName() local targetName = theTarget:getName()
local watchedUnit = guardianAngel.getWatchedUnitByName(targetName) local watchedUnit = guardianAngel.getWatchedUnitByName(targetName)
if not watchedUnit then return end -- fired at some other poor sucker, we don't care 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
-- add it as no threat
local theQItem = guardianAngel.createQItem(theWeapon, theTarget, false) -- 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
-- if we get here, someone fired a guided weapon at my watched units -- if we get here, someone fired a guided weapon at my watched units
-- create a new item for my queue -- create a new item for my queue
local theQItem = guardianAngel.createQItem(theWeapon, theTarget) -- prob 100 local theQItem = guardianAngel.createQItem(theWeapon, theTarget, true) -- this is watched
table.insert(guardianAngel.missilesInTheAir, theQItem) table.insert(guardianAngel.missilesInTheAir, theQItem)
guardianAngel.invokeCallbacks("launch", theQItem.targetName, theQItem.weaponName) guardianAngel.invokeCallbacks("launch", theQItem.targetName, theQItem.weaponName)
@ -420,13 +670,17 @@ function guardianAngel.somethingHappened(event)
local oclock = dcsCommon.clockPositionOfARelativeToB(A, B, unitHeading) local oclock = dcsCommon.clockPositionOfARelativeToB(A, B, unitHeading)
local grpID = theTarget:getGroup():getID() local grpID = theTarget:getGroup():getID()
if guardianAngel.launchWarning then local vbInfo = ""
if guardianAngel.verbose then
vbInfo = ", <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">"
end
if guardianAngel.launchWarning and guardianAngel.active then
-- currently, we always detect immediately -- currently, we always detect immediately
-- can be moved to update() -- can be moved to update()
if guardianAngel.private then if guardianAngel.private then
trigger.action.outTextForGroup(grpID, "Missile, missile, missile, " .. oclock .. " o clock", 30) trigger.action.outTextForGroup(grpID, "Missile, missile, missile, " .. oclock .. " o clock" .. vbInfo, 30)
else else
trigger.action.outText("Missile, missile, missile, " .. oclock .. " o clock", 30) trigger.action.outText("Missile, missile, missile, " .. oclock .. " o clock" .. vbInfo, 30)
end end
theQItem.detected = true -- remember: we detected and warned already theQItem.detected = true -- remember: we detected and warned already
@ -435,6 +689,7 @@ function guardianAngel.somethingHappened(event)
end end
if ID == 2 then if ID == 2 then
if not guardianAngel.active then return end -- we aren't on watch.
if not guardianAngel.intervention then return end -- we don't intervene if not guardianAngel.intervention then return end -- we don't intervene
if not event.weapon then return end -- no weapon, no interest if not event.weapon then return end -- no weapon, no interest
local theWeapon = event.weapon local theWeapon = event.weapon
@ -445,16 +700,37 @@ function guardianAngel.somethingHappened(event)
local theProtegee = nil local theProtegee = nil
for idx, aProt in pairs(guardianAngel.unitsToWatchOver) do for idx, aProt in pairs(guardianAngel.unitsToWatchOver) do
if tName == aProt:getName() then if aProt:isExist() then
theProtegee = aProt 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
end end
end end
if not theProtegee then return end if not theProtegee then return end
-- one of our protegees was hit -- one of our protegees was hit
--trigger.action.outText("+++gA: Protegee " .. tName .. " was hit", 30) --trigger.action.outText("+++gA: Protegee " .. tName .. " was hit", 30)
trigger.action.outText("+++gA: I:" .. theUnit:getName() .. " hit " .. tName .. " with " .. wName, 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)
local qName = guardianAngel.missilesAndTargets[wName]
if qName ~= tName then
trigger.action.outText("+++gA: RETARGET DETECTED", 30)
local wpnTgt = theWeapon:getTarget()
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
end
else
trigger.action.outText("***gA: no missile in the air for <" .. wName .. ">!!!!")
end
-- let's see if the victim was in our list of protected -- let's see if the victim was in our list of protected
-- units -- units
local thePerp = nil local thePerp = nil
@ -479,6 +755,18 @@ function guardianAngel.somethingHappened(event)
-- if we should have protected: mea maxima culpa -- if we should have protected: mea maxima culpa
trigger.action.outText("[+++gA: Angel hangs her head in shame. Mea Culpa, " .. tName.."]", 30) trigger.action.outText("[+++gA: Angel hangs her head in shame. Mea Culpa, " .. tName.."]", 30)
-- see if we can find the q item
local missedItem = guardianAngel.getQItemForWeaponNamed(wName)
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)
end
return return
end end
@ -495,21 +783,102 @@ end
function guardianAngel.update() function guardianAngel.update()
timer.scheduleFunction(guardianAngel.update, {}, timer.getTime() + 1/guardianAngel.ups) timer.scheduleFunction(guardianAngel.update, {}, timer.getTime() + 1/guardianAngel.ups)
-- and break off if nothing to do
if not guardianAngel.active then
guardianAngel.filterMissiles()
return
end
guardianAngel.monitorMissiles() guardianAngel.monitorMissiles()
end end
function guardianAngel.doActivate()
guardianAngel.active = true
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
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()
end
end
if guardianAngel.deactivate then
if cfxZones.testZoneFlag(guardianAngel, guardianAngel.deactivate, "change","lastDeActivate") then
guardianAngel.doDeActivate()
end
end
end
function guardianAngel.collectPlayerUnits() function guardianAngel.collectPlayerUnits()
-- make sure we have all existing player units -- make sure we have all existing player units
-- at start of game -- at start of game
if not guardianAngel.autoAddPlayer then return end -- if not guardianAngel.autoAddPlayer then return end
for i=1, 2 do for i=1, 2 do
-- currently only two factions in dcs -- currently only two factions in dcs
factionUnits = coalition.getPlayers(i) local factionUnits = coalition.getPlayers(i)
for idx, aPlayerUnit in pairs(factionUnits) do for idx, theUnit in pairs(factionUnits) do
-- add all existing faction units local mustProtect = false
guardianAngel.addUnitToWatch(aPlayerUnit) 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 .." contains player unit <" .. theUnit:getName() .. "> -- protect: (" .. dcsCommon.bool2YesNo(mustProtect) .. ")", 30)
end
end
if mustProtect then
guardianAngel.addUnitToWatch(theUnit)
end
end
end
end
function guardianAngel.collectAIUnits()
-- make sure we have all existing AI units
-- at start of game
for i=1, 2 do
-- currently only two factions in dcs
local factionGroups = coalition.getGroups(i)
for idg, aGroup in pairs(factionGroups) do
local factionUnits = aGroup:getUnits()
for idx, theUnit in pairs(factionUnits) do
local mustProtect = false
local gCat = aGroup:getCategory()
if gCat == 0 or gCat == 1 then
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 AI unit <" .. theUnit:getName() .. ">, protect it: " .. dcsCommon.bool2YesNo(mustProtect) .. ".", 30)
end
end
if mustProtect then
guardianAngel.addUnitToWatch(theUnit)
end
end
end
end end
end end
end end
@ -521,13 +890,10 @@ function guardianAngel.readConfigZone()
-- note: must match exactly!!!! -- note: must match exactly!!!!
local theZone = cfxZones.getZoneByName("guardianAngelConfig") local theZone = cfxZones.getZoneByName("guardianAngelConfig")
if not theZone then if not theZone then
trigger.action.outText("+++gA: no config zone!", 30) theZone = cfxZones.createSimpleZone("guardianAngelConfig")
return
end
if guardianAngel.verbose then
trigger.action.outText("+++gA: found config zone!", 30)
end end
guardianAngel.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) guardianAngel.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
guardianAngel.autoAddPlayer = cfxZones.getBoolFromZoneProperty(theZone, "autoAddPlayer", true) guardianAngel.autoAddPlayer = cfxZones.getBoolFromZoneProperty(theZone, "autoAddPlayer", true)
@ -537,8 +903,52 @@ function guardianAngel.readConfigZone()
guardianAngel.private = cfxZones.getBoolFromZoneProperty(theZone, "private", false) guardianAngel.private = cfxZones.getBoolFromZoneProperty(theZone, "private", false)
guardianAngel.explosion = cfxZones.getNumberFromZoneProperty(theZone, "explosion", -1) guardianAngel.explosion = cfxZones.getNumberFromZoneProperty(theZone, "explosion", -1)
guardianAngel.fxDistance = cfxZones.getNumberFromZoneProperty(theZone, "fxDistance", 500) guardianAngel.fxDistance = cfxZones.getNumberFromZoneProperty(theZone, "fxDistance", 500)
guardianAngel.active = cfxZones.getBoolFromZoneProperty(theZone, "active", true)
if cfxZones.hasProperty(theZone, "activate?") then
guardianAngel.activate = cfxZones.getStringFromZoneProperty(theZone, "activate?", "*<none>")
guardianAngel.lastActivate = cfxZones.getFlagValue(guardianAngel.activate, theZone)
elseif cfxZones.hasProperty(theZone, "on?") then
guardianAngel.activate = cfxZones.getStringFromZoneProperty(theZone, "on?", "*<none>")
guardianAngel.lastActivate = cfxZones.getFlagValue(guardianAngel.activate, theZone)
end
if cfxZones.hasProperty(theZone, "deactivate?") then
guardianAngel.deactivate = cfxZones.getStringFromZoneProperty(theZone, "deactivate?", "*<none>")
guardianAngel.lastDeActivate = cfxZones.getFlagValue(guardianAngel.deactivate, theZone)
elseif cfxZones.hasProperty(theZone, "off?") then
guardianAngel.deactivate = cfxZones.getStringFromZoneProperty(theZone, "off?", "*<none>")
guardianAngel.lastDeActivate = cfxZones.getFlagValue(guardianAngel.deactivate, theZone)
end
guardianAngel.configZone = theZone
if guardianAngel.verbose then
trigger.action.outText("+++gA: processed config zone", 30)
end
end end
--
-- guardian zones
--
function guardianAngel.processGuardianZone(theZone)
theZone.angelic = cfxZones.getBoolFromZoneProperty(theZone, "guardian", true)
if theZone.verbose or guardianAngel.verbose then
trigger.action.outText("+++gA: processed 'guardian' zone <" .. theZone.name .. ">", 30)
end
-- add it to my angelicZones
table.insert(guardianAngel.angelicZones, theZone)
end
function guardianAngel.readGuardianZones()
local attrZones = cfxZones.getZonesWithAttributeNamed("guardian")
for k, aZone in pairs(attrZones) do
guardianAngel.processGuardianZone(aZone)
end
end
-- --
-- start -- start
@ -553,6 +963,9 @@ function guardianAngel.start()
-- read config -- read config
guardianAngel.readConfigZone() guardianAngel.readConfigZone()
-- read guarded zones
guardianAngel.readGuardianZones()
-- install event monitor -- install event monitor
dcsCommon.addEventHandler(guardianAngel.somethingHappened, dcsCommon.addEventHandler(guardianAngel.somethingHappened,
guardianAngel.preProcessor, guardianAngel.preProcessor,
@ -560,10 +973,14 @@ function guardianAngel.start()
-- collect all units that are already in the game at this point -- collect all units that are already in the game at this point
guardianAngel.collectPlayerUnits() guardianAngel.collectPlayerUnits()
guardianAngel.collectAIUnits()
-- start update -- start update
guardianAngel.update() guardianAngel.update()
-- start flag check
guardianAngel.flagUpdate()
trigger.action.outText("Guardian Angel v" .. guardianAngel.version .. " running", 30) trigger.action.outText("Guardian Angel v" .. guardianAngel.version .. " running", 30)
return true return true
end end
@ -581,3 +998,9 @@ end
-- test callback -- test callback
--guardianAngel.addCallback(guardianAngel.testCB) --guardianAngel.addCallback(guardianAngel.testCB)
--guardianAngel.invokeCallbacks("A", "B", "C") --guardianAngel.invokeCallbacks("A", "B", "C")
--[[--
to do
- turn on and off via flags
- zones that designate protected/unprotected aircraft
--]]--

View File

@ -1,5 +1,5 @@
messenger = {} messenger = {}
messenger.version = "1.3.1" messenger.version = "1.3.2"
messenger.verbose = false messenger.verbose = false
messenger.requiredLibs = { messenger.requiredLibs = {
"dcsCommon", -- always "dcsCommon", -- always
@ -22,7 +22,9 @@ messenger.messengers = {}
1.2.1 - qoL: <n> = newline, <z> = zone name, <v> = value 1.2.1 - qoL: <n> = newline, <z> = zone name, <v> = value
1.3.0 - messenger? saves messageOut? attribute 1.3.0 - messenger? saves messageOut? attribute
1.3.1 - message now can interpret value as time with <h> <m> <s> <:h> <:m> <:s> 1.3.1 - message now can interpret value as time with <h> <m> <s> <:h> <:m> <:s>
1.3.2 - message interprets <t> as time in HH:MM:SS of current time
- can interpret <lat>, <lon>, <mgrs>
- zone-local verbosity
--]]-- --]]--
function messenger.addMessenger(theZone) function messenger.addMessenger(theZone)
@ -54,6 +56,24 @@ function messenger.preProcMessage(inMsg, theZone)
if theZone then if theZone then
outMsg = outMsg:gsub("<z>", theZone.name) outMsg = outMsg:gsub("<z>", theZone.name)
end end
-- replace <t> with current mission time HMS
local absSecs = timer.getAbsTime()-- + env.mission.start_time
while absSecs > 86400 do
absSecs = absSecs - 86400 -- subtract out all days
end
local timeString = dcsCommon.processHMS("<:h>:<:m>:<:s>", absSecs)
outMsg = outMsg:gsub("<t>", timeString)
-- replace <lat> with lat of zone point and <lon> with lon of zone point
-- and <mgrs> with mgrs coords of zone point
local currPoint = cfxZones.getPoint(theZone)
local lat, lon, alt = coord.LOtoLL(currPoint)
lat, lon = dcsCommon.latLon2Text(lat, lon)
outMsg = outMsg:gsub("<lat>", lat)
outMsg = outMsg:gsub("<lon>", lon)
local grid = coord.LLtoMGRS(coord.LOtoLL(currPoint))
local mgrs = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
outMsg = outMsg:gsub("<mgrs>", mgrs)
return outMsg return outMsg
end end
@ -133,7 +153,7 @@ function messenger.createMessengerWithZone(theZone)
theZone.messageValue = cfxZones.getStringFromZoneProperty(theZone, "messageValue?", "<none>") theZone.messageValue = cfxZones.getStringFromZoneProperty(theZone, "messageValue?", "<none>")
end end
if messenger.verbose then if messenger.verbose or theZone.verbose then
trigger.action.outText("+++Msg: new zone <".. theZone.name .."> will say <".. theZone.message .. ">", 30) trigger.action.outText("+++Msg: new zone <".. theZone.name .."> will say <".. theZone.message .. ">", 30)
end end
end end
@ -167,7 +187,7 @@ end
function messenger.isTriggered(theZone) function messenger.isTriggered(theZone)
-- this module has triggered -- this module has triggered
if theZone.messageOff then if theZone.messageOff then
if messenger.verbose then if messenger.verbose or theZone.verbose then
trigger.action.outFlag("msg: message for <".. theZone.name .."> is OFF",30) trigger.action.outFlag("msg: message for <".. theZone.name .."> is OFF",30)
end end
return return
@ -175,7 +195,7 @@ function messenger.isTriggered(theZone)
local fileName = "l10n/DEFAULT/" .. theZone.soundFile local fileName = "l10n/DEFAULT/" .. theZone.soundFile
local msg = messenger.getMessage(theZone) local msg = messenger.getMessage(theZone)
if messenger.verbose then if messenger.verbose or theZone.verbose then
trigger.action.outText("+++Msg: <".. theZone.name .."> will say <".. msg .. ">", 30) trigger.action.outText("+++Msg: <".. theZone.name .."> will say <".. msg .. ">", 30)
end end
@ -200,7 +220,7 @@ function messenger.update()
-- make sure to re-start before reading time limit -- make sure to re-start before reading time limit
-- new trigger code -- new trigger code
if cfxZones.testZoneFlag(aZone, aZone.triggerMessagerFlag, aZone.msgTriggerMethod, "lastMessageTriggerValue") then if cfxZones.testZoneFlag(aZone, aZone.triggerMessagerFlag, aZone.msgTriggerMethod, "lastMessageTriggerValue") then
if messenger.verbose then if messenger.verbose or aZone.verbose then
trigger.action.outText("+++msgr: triggered on in? for <".. aZone.name ..">", 30) trigger.action.outText("+++msgr: triggered on in? for <".. aZone.name ..">", 30)
end end
messenger.isTriggered(aZone) messenger.isTriggered(aZone)
@ -209,14 +229,14 @@ function messenger.update()
-- old trigger code -- old trigger code
if cfxZones.testZoneFlag(aZone, aZone.messageOffFlag, aZone.msgTriggerMethod, "lastMessageOff") then if cfxZones.testZoneFlag(aZone, aZone.messageOffFlag, aZone.msgTriggerMethod, "lastMessageOff") then
aZone.messageOff = true aZone.messageOff = true
if messenger.verbose then if messenger.verbose or aZone.verbose then
trigger.action.outText("+++msg: messenger <" .. aZone.name .. "> turned ***OFF***", 30) trigger.action.outText("+++msg: messenger <" .. aZone.name .. "> turned ***OFF***", 30)
end end
end end
if cfxZones.testZoneFlag(aZone, aZone.messageOnFlag, aZone.msgTriggerMethod, "lastMessageOn") then if cfxZones.testZoneFlag(aZone, aZone.messageOnFlag, aZone.msgTriggerMethod, "lastMessageOn") then
aZone.messageOff = false aZone.messageOff = false
if messenger.verbose then if messenger.verbose or aZone.verbose then
trigger.action.outText("+++msg: messenger <" .. aZone.name .. "> turned ON", 30) trigger.action.outText("+++msg: messenger <" .. aZone.name .. "> turned ON", 30)
end end
end end

View File

@ -1,5 +1,5 @@
radioMenu = {} radioMenu = {}
radioMenu.version = "1.0.0" radioMenu.version = "1.0.1"
radioMenu.verbose = false radioMenu.verbose = false
radioMenu.ups = 1 radioMenu.ups = 1
radioMenu.requiredLibs = { radioMenu.requiredLibs = {
@ -11,6 +11,7 @@ radioMenu.menus = {}
--[[-- --[[--
Version History Version History
1.0.0 Initial version 1.0.0 Initial version
1.0.1 spelling corrections
--]]-- --]]--
function radioMenu.addRadioMenu(theZone) function radioMenu.addRadioMenu(theZone)
@ -181,7 +182,7 @@ function radioMenu.doMenuX(args)
cfxZones.pollFlag(theFlag, theZone.radioMethod, theZone) cfxZones.pollFlag(theFlag, theZone.radioMethod, theZone)
if theZone.verbose or radioMenu.verbose then if theZone.verbose or radioMenu.verbose then
trigger.action.outText("+++menu: banging D! with <" .. theZone.radioMethod .. "> on <" .. theFlag .. "> for " .. theZone.name, 30) trigger.action.outText("+++menu: banging with <" .. theZone.radioMethod .. "> on <" .. theFlag .. "> for " .. theZone.name, 30)
end end
end end

Binary file not shown.