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.version = "2.2.0"
cfxArtilleryZones.version = "2.2.1"
cfxArtilleryZones.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -29,6 +29,7 @@ cfxArtilleryZones.verbose = false
2.1.0 - DML Flag Support
- code cleanup
2.2.0 - DML Watchflag integration
2.2.1 - minor code clean-up
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
@ -136,11 +137,7 @@ function cfxArtilleryZones.processArtilleryZone(aZone)
if cfxZones.hasProperty(aZone, "f?") then
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "f?", "none")
end
--[[--
if cfxZones.hasProperty(aZone, "triggerFlag") then
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "triggerFlag", "none")
end
--]]--
if cfxZones.hasProperty(aZone, "artillery?") then
aZone.artyTriggerFlag = cfxZones.getStringFromZoneProperty(aZone, "artillery?", "none")
end

View File

@ -1,11 +1,16 @@
cfxReconMode = {}
cfxReconMode.version = "1.5.0"
cfxReconMode.version = "2.0.0"
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.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.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.activeMarks = {} -- all marks and their groups, indexed by groupName
@ -48,6 +53,26 @@ VERSION HISTORY
1.5.0 - removeWhenDestroyed()
- autoRemove()
- 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
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.prioFlag = nil
cfxReconMode.detectFlag = nil
cfxReconMode.method = "inc"
cfxReconMode.applyMarks = true
cfxReconMode.mgrs = false
cfxReconMode.ups = 1 -- updates per second.
cfxReconMode.scouts = {} -- units that are performing scouting.
@ -111,38 +138,80 @@ function cfxReconMode.invokeCallbacks(reason, theSide, theScout, theGroup, theNa
end
-- 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 type(aGroup) == "table" and aGroup.getName then
aGroup = aGroup:getName()
end
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
function cfxReconMode.addToBlackList(aGroup)
function cfxReconMode.addToBlackList(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
table.insert(cfxReconMode.blackList, aGroup)
--table.insert(cfxReconMode.blackList, aGroup)
cfxReconMode.blackList[aGroup] = aGroup
cfxReconMode.dynamics[aGroup] = dynamic
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)
if not theString then return false end
if not theList then return false end
if type(theString) == "string" then
for idx,anItem in pairs(theList) do
if anItem == theString then return true end
-- returns two values: inList, and original group name (if exist)
if not theString then return false, nil end
if type(theString) ~= "string" then return false, nil end
if not theList then return false, nil 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
return false
end
return false, nil
end
-- addScout directly adds a scout unit. Use from external
-- 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
@ -205,7 +274,7 @@ end
function cfxReconMode.removeScout(theUnit)
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
end
@ -265,7 +334,8 @@ function cfxReconMode.placeMarkForUnit(location, theSide, theGroup)
local theID = cfxReconMode.uuid()
local theDesc = "Contact: "..theGroup:getName()
if cfxReconMode.reportNumbers then
theDesc = theDesc .. " (" .. theGroup:getSize() .. " units)"
-- theDesc = theDesc .. " (" .. theGroup:getSize() .. " units)"
theDesc = theDesc .. " - " .. cfxReconMode.getSit(theGroup) .. ", " .. cfxReconMode.getAction(theGroup) .. "."
end
trigger.action.markToCoalition(
theID,
@ -296,6 +366,121 @@ function cfxReconMode.removeMarkForArgs(args)
cfxReconMode.detectedGroups[theName] = nil -- some housekeeping.
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)
-- put a mark on the map
@ -312,26 +497,51 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
-- say something
if cfxReconMode.announcer then
trigger.action.outTextForCoalition(mySide, theScout:getName() .. " reports new ground contact " .. theGroup:getName(), 30)
trigger.action.outText("+++recon: announced for side " .. mySide, 30)
local msg = cfxReconMode.generateSALT(theScout, theGroup)
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
trigger.action.outSoundForCoalition(mySide, cfxReconMode.reconSound)
else
--trigger.action.outText("+++recon: announcer off", 30)
end
-- see if it was a prio target
if cfxReconMode.isStringInList(theGroup:getName(), cfxReconMode.prioList) then
if cfxReconMode.announcer then
trigger.action.outTextForCoalition(mySide, "Priority target confirmed", 30)
local inList, gName = cfxReconMode.isStringInList(theGroup:getName(), cfxReconMode.prioList)
if inList then
-- if cfxReconMode.announcer then
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: Priority target spotted", 30)
end
-- invoke callbacks
cfxReconMode.invokeCallbacks("priotity", mySide, theScout, theGroup, theGroup:getName())
cfxReconMode.invokeCallbacks("priority", mySide, theScout, theGroup, theGroup:getName())
-- increase prio flag
if cfxReconMode.prioFlag then
local currVal = trigger.misc.getUserFlag(cfxReconMode.prioFlag)
trigger.action.setUserFlag(cfxReconMode.prioFlag, currVal + 1)
cfxZones.pollFlag(cfxReconMode.prioFlag, cfxReconMode.method, cfxReconMode.theZone)
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
else
-- invoke callbacks
@ -339,8 +549,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
-- increase normal flag
if cfxReconMode.detectFlag then
local currVal = trigger.misc.getUserFlag(cfxReconMode.detectFlag)
trigger.action.setUserFlag(cfxReconMode.detectFlag, currVal + 1)
cfxZones.pollFlag(cfxReconMode.detectFlag, cfxReconMode.method, cfxReconMode.theZone)
end
end
end
@ -371,7 +580,8 @@ function cfxReconMode.performReconForUnit(theScout)
local groupName = theGroup:getName()
if cfxReconMode.detectedGroups[groupName] == nil then
-- 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
-- perhaps add some percent chance now
-- remember that we know this group
@ -464,11 +674,62 @@ function cfxReconMode.autoRemove()
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
function cfxReconMode:onEvent(event)
if not event 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
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
-- every so often when they do not exist
@ -481,6 +742,20 @@ function cfxReconMode:onEvent(event)
-- scout when they are on that side. in that case
-- you must add manually
local theSide = theUnit:getCoalition()
local isPlayer = theUnit:getPlayerName()
if isPlayer then
-- since players wake up late, we lazy-eval their group
-- and add it to the blind/scout lists
cfxReconMode.lateEvalPlayerUnit(theUnit)
if cfxReconMode.verbose then
trigger.action.outText("+++rcn: late player check complete for <" .. theUnit:getName() .. ">", 30)
end
else
isPlayer = false -- safer than sorry
end
if cfxReconMode.autoRecon then
if theSide == 0 and not cfxReconMode.greyScouts then
return -- grey scouts are not allowed
end
@ -492,15 +767,46 @@ function cfxReconMode:onEvent(event)
end
if cfxReconMode.playerOnlyRecon then
if not theUnit:getPlayerName() 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
-- 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
trigger.action.outText("+++rcn: event " .. event.id .. " for unit " .. theUnit:getName(), 30)
end
cfxReconMode.addScout(theUnit)
end
-- trigger.action.outText("+++rcn-onEvent: " .. event.id .. " for <" .. theUnit:getName() .. ">", 30)
end
--
@ -512,9 +818,38 @@ function cfxReconMode.processScoutGroups(theGroups)
-- we are very early in the mission, only few groups really
-- exist now, the rest of the units come in with 15 event
if aGroup:isExist() then
-- see if we want to add these aircraft to the
-- active scout list
local gName = aGroup:getName()
local isBlind, ignored = cfxReconMode.isStringInList(gName, cfxReconMode.blindScouts)
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)
@ -522,25 +857,34 @@ function cfxReconMode.processScoutGroups(theGroups)
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
function cfxReconMode.initScouts()
-- get all groups of aircraft. Unrolled loop 0..2
-- added helicopters, removed check for grey/red/bluescouts,
-- as that happens in processScoutGroups
local theAirGroups = {}
if cfxReconMode.greyScouts then
theAirGroups = coalition.getGroups(0, 0) -- 0 = aircraft
cfxReconMode.processScoutGroups(theAirGroups)
end
if cfxReconMode.redScouts then
theAirGroups = coalition.getGroups(1, 0) -- 1 = red, 0 = aircraft
theAirGroups = coalition.getGroups(0, 1) -- 1 = helicopter
cfxReconMode.processScoutGroups(theAirGroups)
end
if cfxReconMode.blueScouts then
theAirGroups = coalition.getGroups(2, 0) -- 2 = blue, 0 = aircraft
theAirGroups = coalition.getGroups(1, 0) -- 0 = aircraft
cfxReconMode.processScoutGroups(theAirGroups)
theAirGroups = coalition.getGroups(1, 1) -- 1 = helicopter
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
cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio+", "none")
elseif cfxZones.hasProperty(theZone, "prio!") then
cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio!", "*<none>")
end
if cfxZones.hasProperty(theZone, "detect+") then
cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect+", "none")
elseif cfxZones.hasProperty(theZone, "detect!") then
cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect!", "*<none>")
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.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
@ -591,6 +943,101 @@ function cfxReconMode.readConfigZone()
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
--
@ -606,6 +1053,12 @@ function cfxReconMode.start()
-- read config
cfxReconMode.readConfigZone()
-- gather prio and blacklist groups
cfxReconMode.readReconGroups()
-- gather allowed and forbidden scouts
cfxReconMode.readScoutGroups()
-- gather exiting planes
cfxReconMode.initScouts()
@ -618,7 +1071,7 @@ function cfxReconMode.start()
cfxReconMode.autoRemove()
end
if cfxReconMode.autoRecon then
if true or cfxReconMode.autoRecon then
-- install own event handler to detect
-- when a unit takes off and add it to scout
-- roster
@ -651,8 +1104,9 @@ ideas:
- renew lease. when already sighted, simply renew lease, maybe update location.
- update marks and renew lease
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
local lastVal = theZone[latchName]
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
end

View File

@ -1,5 +1,5 @@
dcsCommon = {}
dcsCommon.version = "2.6.4"
dcsCommon.version = "2.6.5"
--[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB
@ -74,6 +74,9 @@ dcsCommon.version = "2.6.4"
2.6.2 - new combineTables()
2.6.3 - new tacan2freq()
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"
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)
-- o'clock notation
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 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
heading = heading + 2 * math.pi -- put heading in range of 0 to 2*pi
end
@ -2255,6 +2277,50 @@ function dcsCommon.coalition2county(inCoalition)
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

View File

@ -1,5 +1,5 @@
guardianAngel = {}
guardianAngel.version = "2.0.3"
guardianAngel.version = "3.0.0"
guardianAngel.ups = 10
guardianAngel.launchWarning = true -- detect launches and warn pilot
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.autoAddPlayers = true
guardianAngel.active = true -- can be turned on / off
guardianAngel.angelicZones = {}
guardianAngel.requiredLibs = {
"dcsCommon", -- always
"cfxZones", -- Zones, of course
@ -37,6 +41,18 @@ guardianAngel.requiredLibs = {
- 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
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.missilesInTheAir = {} -- missiles in the air
guardianAngel.missilesAndTargets = {} -- permanent log which missile was aimed at whom
guardianAngel.callBacks = {} -- callbacks
-- callback signature: callBack(reason, targetUnitName, weaponName)
-- reasons (string): "launch", "miss", "reacquire", "trackloss", "disappear", "intervention"
@ -77,9 +93,14 @@ function guardianAngel.addUnitToWatch(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
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
@ -104,14 +125,17 @@ end
--
-- watch q items
--
function guardianAngel.createQItem(theWeapon, theTarget, detectProbability)
function guardianAngel.createQItem(theWeapon, theTarget, threat)
if not theWeapon then return nil end
if not theTarget 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 = {}
theItem.theWeapon = theWeapon
theItem.wP = theWeapon:getPoint() -- save location
theItem.theWeapon = theWeapon -- weapon that we are tracking
--theItem.wP = theWeapon:getPoint() -- save location
theItem.weaponName = theWeapon:getName()
theItem.theTarget = theTarget
theItem.tGroup = theTarget:getGroup()
@ -119,14 +143,63 @@ function guardianAngel.createQItem(theWeapon, theTarget, detectProbability)
theItem.targetName = theTarget:getName()
theItem.launchTimeStamp = timer.getTime()
theItem.lastCheckTimeStamp = -1000
--theItem.lastCheckTimeStamp = -1000
theItem.lastDistance = math.huge
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.threat = threat
theItem.lastDesc = "(new)"
theItem.timeStamp = timer.getTime()
return theItem
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
function guardianAngel.calcSafeExplosionPoint(wpn, pln, dist)
@ -138,6 +211,7 @@ function guardianAngel.calcSafeExplosionPoint(wpn, pln, dist)
return newPoint
end
--[[--
function guardianAngel.bubbleCheck(wPos, w)
if true then return false end
for idx, aProtectee in pairs (guardianAngel.unitsToWatchOver) do
@ -154,41 +228,102 @@ function guardianAngel.bubbleCheck(wPos, w)
end
return false
end
--]]--
function guardianAngel.monitorItem(theItem)
local w = theItem.theWeapon
local ID = theItem.tID
if not w then return false end
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"
if guardianAngel.announcer then
if guardianAngel.announcer and theItem.threat then
if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, 30)
else
trigger.action.outText(desc, 30)
end
end
guardianAngel.invokeCallbacks("disappear", theItem.targetName, theItem.weaponName)
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)
-- end
return false
end
local t = theItem.theTarget
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
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
local bubbleThreat = guardianAngel.bubbleCheck(A, w)
-- 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 .. ": "
if t == currentTarget then
if true or t == currentTarget then
desc = desc .. "tracking " .. theItem.targetName .. ", d = " .. d .. "m"
local vcc = dcsCommon.getClosingVelocity(t, w)
desc = desc .. ", Vcc = " .. math.floor(vcc) .. "m/s"
@ -200,13 +335,15 @@ function guardianAngel.monitorItem(theItem)
-- destroy the missile
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 theItem.lostTrack then desc = desc .. " (little sneak!)" end
if theItem.missed then desc = desc .. " (missed you!)" end
--if theItem.lostTrack then desc = desc .. " (little sneak!)" end
--if theItem.missed then desc = desc .. " (missed you!)" end
if guardianAngel.announcer then
if guardianAngel.private then
@ -232,8 +369,8 @@ function guardianAngel.monitorItem(theItem)
d <= guardianAngel.minMissileDist -- god's override
then
desc = desc .. " GOD INTERVENTION"
if theItem.lostTrack then desc = desc .. " (little sneak!)" end
if theItem.missed then desc = desc .. " (missed you!)" end
--if theItem.lostTrack then desc = desc .. " (little sneak!)" end
--if theItem.missed then desc = desc .. " (missed you!)" end
if guardianAngel.announcer then
if guardianAngel.private then
@ -251,6 +388,7 @@ function guardianAngel.monitorItem(theItem)
return false -- remove from list
end
else
--[[--
if not theItem.lostTrack then
desc = desc .. "Missile LOST TRACK"
@ -264,10 +402,12 @@ function guardianAngel.monitorItem(theItem)
guardianAngel.invokeCallbacks("trackloss", theItem.targetName, theItem.weaponName)
theItem.lostTrack = true
end
theItem.lastDistance = d
return true -- true because they can re-acquire!
--]]--
-- theItem.lastDistance = d
-- return true -- true because they can re-acquire!
end
--[[--
if d > theItem.lastDistance then
-- this can be wrong because if a missile is launched
-- at an angle, it can initially look as if it missed
@ -287,7 +427,8 @@ function guardianAngel.monitorItem(theItem)
theItem.lastDistance = d
return true -- better not disregard - they can re-acquire!
end
--]]--
--[[--
if theItem.missed and d < theItem.lastDistance then
desc = desc .. " Missile RE-ACQUIRED!"
@ -301,8 +442,9 @@ function guardianAngel.monitorItem(theItem)
theItem.missed = false
guardianAngel.invokeCallbacks("reacquire", theItem.targetName, theItem.weaponName)
end
--]]--
theItem.lastDistance = d
-- theItem.lastDistance = d
return true
end
@ -316,7 +458,7 @@ function guardianAngel.monitorMissiles()
-- guardianAngel.detectItem(anItem)
-- see if the weapon is still in existence
stillAlive = guardianAngel.monitorItem(anItem)
local stillAlive = guardianAngel.monitorItem(anItem)
if stillAlive then
table.insert(newArray, anItem)
end
@ -324,6 +466,31 @@ function guardianAngel.monitorMissiles()
guardianAngel.missilesInTheAir = newArray
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
--
@ -348,22 +515,79 @@ 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
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
local ID = event.id
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
-- this is a player created unit
if guardianAngel.verbose then
trigger.action.outText("+++gA: unit born " .. theUnit:getName(), 30)
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
-- 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
end
@ -372,9 +596,23 @@ function guardianAngel.somethingHappened(event)
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
@ -390,6 +628,7 @@ function guardianAngel.somethingHappened(event)
if ID == 1 then
-- even if not active, we collect missile data
-- someone shot something. see if it is fire directed at me
local theWeapon = event.weapon
local theTarget
@ -406,11 +645,22 @@ function guardianAngel.somethingHappened(event)
-- if we get here, we have weapon aimed at a target
local targetName = theTarget:getName()
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
-- 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)
guardianAngel.invokeCallbacks("launch", theQItem.targetName, theQItem.weaponName)
@ -420,13 +670,17 @@ function guardianAngel.somethingHappened(event)
local oclock = dcsCommon.clockPositionOfARelativeToB(A, B, unitHeading)
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
-- can be moved to update()
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
trigger.action.outText("Missile, missile, missile, " .. oclock .. " o clock", 30)
trigger.action.outText("Missile, missile, missile, " .. oclock .. " o clock" .. vbInfo, 30)
end
theQItem.detected = true -- remember: we detected and warned already
@ -435,6 +689,7 @@ function guardianAngel.somethingHappened(event)
end
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 event.weapon then return end -- no weapon, no interest
local theWeapon = event.weapon
@ -445,16 +700,37 @@ 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
else
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)
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
-- units
local thePerp = nil
@ -479,6 +755,18 @@ function guardianAngel.somethingHappened(event)
-- 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)
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
end
@ -495,21 +783,102 @@ end
function guardianAngel.update()
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()
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()
-- make sure we have all existing player units
-- at start of game
if not guardianAngel.autoAddPlayer then return end
-- if not guardianAngel.autoAddPlayer then return end
for i=1, 2 do
-- currently only two factions in dcs
factionUnits = coalition.getPlayers(i)
for idx, aPlayerUnit in pairs(factionUnits) do
-- add all existing faction units
guardianAngel.addUnitToWatch(aPlayerUnit)
local factionUnits = coalition.getPlayers(i)
for idx, theUnit in pairs(factionUnits) do
local mustProtect = false
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
@ -521,13 +890,10 @@ function guardianAngel.readConfigZone()
-- note: must match exactly!!!!
local theZone = cfxZones.getZoneByName("guardianAngelConfig")
if not theZone then
trigger.action.outText("+++gA: no config zone!", 30)
return
end
if guardianAngel.verbose then
trigger.action.outText("+++gA: found config zone!", 30)
theZone = cfxZones.createSimpleZone("guardianAngelConfig")
end
guardianAngel.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
guardianAngel.autoAddPlayer = cfxZones.getBoolFromZoneProperty(theZone, "autoAddPlayer", true)
@ -537,8 +903,52 @@ function guardianAngel.readConfigZone()
guardianAngel.private = cfxZones.getBoolFromZoneProperty(theZone, "private", false)
guardianAngel.explosion = cfxZones.getNumberFromZoneProperty(theZone, "explosion", -1)
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
--
-- 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
@ -553,6 +963,9 @@ function guardianAngel.start()
-- read config
guardianAngel.readConfigZone()
-- read guarded zones
guardianAngel.readGuardianZones()
-- install event monitor
dcsCommon.addEventHandler(guardianAngel.somethingHappened,
guardianAngel.preProcessor,
@ -560,10 +973,14 @@ function guardianAngel.start()
-- collect all units that are already in the game at this point
guardianAngel.collectPlayerUnits()
guardianAngel.collectAIUnits()
-- start update
guardianAngel.update()
-- start flag check
guardianAngel.flagUpdate()
trigger.action.outText("Guardian Angel v" .. guardianAngel.version .. " running", 30)
return true
end
@ -581,3 +998,9 @@ end
-- test callback
--guardianAngel.addCallback(guardianAngel.testCB)
--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.version = "1.3.1"
messenger.version = "1.3.2"
messenger.verbose = false
messenger.requiredLibs = {
"dcsCommon", -- always
@ -22,7 +22,9 @@ messenger.messengers = {}
1.2.1 - qoL: <n> = newline, <z> = zone name, <v> = value
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.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)
@ -54,6 +56,24 @@ function messenger.preProcMessage(inMsg, theZone)
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)
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
@ -133,7 +153,7 @@ function messenger.createMessengerWithZone(theZone)
theZone.messageValue = cfxZones.getStringFromZoneProperty(theZone, "messageValue?", "<none>")
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)
end
end
@ -167,7 +187,7 @@ end
function messenger.isTriggered(theZone)
-- this module has triggered
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)
end
return
@ -175,7 +195,7 @@ function messenger.isTriggered(theZone)
local fileName = "l10n/DEFAULT/" .. theZone.soundFile
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)
end
@ -200,7 +220,7 @@ function messenger.update()
-- make sure to re-start before reading time limit
-- new trigger code
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)
end
messenger.isTriggered(aZone)
@ -209,14 +229,14 @@ function messenger.update()
-- old trigger code
if cfxZones.testZoneFlag(aZone, aZone.messageOffFlag, aZone.msgTriggerMethod, "lastMessageOff") then
aZone.messageOff = true
if messenger.verbose then
if messenger.verbose or aZone.verbose then
trigger.action.outText("+++msg: messenger <" .. aZone.name .. "> turned ***OFF***", 30)
end
end
if cfxZones.testZoneFlag(aZone, aZone.messageOnFlag, aZone.msgTriggerMethod, "lastMessageOn") then
aZone.messageOff = false
if messenger.verbose then
if messenger.verbose or aZone.verbose then
trigger.action.outText("+++msg: messenger <" .. aZone.name .. "> turned ON", 30)
end
end

View File

@ -1,5 +1,5 @@
radioMenu = {}
radioMenu.version = "1.0.0"
radioMenu.version = "1.0.1"
radioMenu.verbose = false
radioMenu.ups = 1
radioMenu.requiredLibs = {
@ -11,6 +11,7 @@ radioMenu.menus = {}
--[[--
Version History
1.0.0 Initial version
1.0.1 spelling corrections
--]]--
function radioMenu.addRadioMenu(theZone)
@ -181,7 +182,7 @@ function radioMenu.doMenuX(args)
cfxZones.pollFlag(theFlag, theZone.radioMethod, theZone)
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

Binary file not shown.