Version 1.4.1

Civ Air off-map locations
Guardian Angel updates
Recon Mode updates
This commit is contained in:
Christian Franz 2023-08-10 11:45:07 +02:00
parent 3117bfd80c
commit 7c6deec7a6
11 changed files with 704 additions and 206 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
cfxReconMode = {} cfxReconMode = {}
cfxReconMode.version = "2.1.4" cfxReconMode.version = "2.2.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
@ -86,6 +86,10 @@ VERSION HISTORY
2.1.3 - added cfxReconMode.name to allow direct acces with test zone flag 2.1.3 - added cfxReconMode.name to allow direct acces with test zone flag
2.1.4 - canDetect() also checks if unit has been activated 2.1.4 - canDetect() also checks if unit has been activated
canDetect has strenghtened isExist() guard canDetect has strenghtened isExist() guard
2.2.0 - new marksLocked config attribute, defaults to false
- new marksFadeAfter config attribute to control mark time
- dmlZones OOP upgrade
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
@ -355,7 +359,7 @@ function cfxReconMode.placeMarkForUnit(location, theSide, theGroup)
theDesc, theDesc,
location, location,
theSide, theSide,
false, cfxReconMode.marksLocked, -- readOnly -- false,
nil) nil)
return theID return theID
end end
@ -492,7 +496,7 @@ function cfxReconMode.processZoneMessage(inMsg, theZone, theGroup)
-- replace <lat> with lat of zone point and <lon> with lon of zone point -- replace <lat> with lat of zone point and <lon> with lon of zone point
-- and <mgrs> with mgrs coords of zone point -- and <mgrs> with mgrs coords of zone point
local currPoint = cfxZones.getPoint(theZone) local currPoint = theZone:getPoint()
if theGroup and theGroup:isExist() then if theGroup and theGroup:isExist() then
-- only use group's point when group exists and alive -- only use group's point when group exists and alive
local theUnit = dcsCommon.getFirstLivingUnit(theGroup) local theUnit = dcsCommon.getFirstLivingUnit(theGroup)
@ -529,7 +533,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
end end
-- put a mark on the map -- put a mark on the map
if not silent and cfxReconMode.applyMarks then if (not silent) and cfxReconMode.applyMarks then
local theID = cfxReconMode.placeMarkForUnit(theLoc, mySide, theGroup) local theID = cfxReconMode.placeMarkForUnit(theLoc, mySide, theGroup)
local gName = theGroup:getName() local gName = theGroup:getName()
local args = {mySide, theScout, theGroup, theID, gName} local args = {mySide, theScout, theGroup, theID, gName}
@ -541,10 +545,9 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
end end
-- say something -- say something
if not silent and cfxReconMode.announcer then if (not silent) and cfxReconMode.announcer then
local msg = cfxReconMode.generateSALT(theScout, theGroup) local msg = cfxReconMode.generateSALT(theScout, theGroup)
trigger.action.outTextForCoalition(mySide, msg, cfxReconMode.reportTime) trigger.action.outTextForCoalition(mySide, msg, cfxReconMode.reportTime)
-- trigger.action.outTextForCoalition(mySide, theScout:getName() .. " reports new ground contact " .. theGroup:getName(), 30)
if cfxReconMode.verbose then if cfxReconMode.verbose then
trigger.action.outText("+++rcn: announced for side " .. mySide, 30) trigger.action.outText("+++rcn: announced for side " .. mySide, 30)
end end
@ -554,7 +557,6 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
end end
-- see if it was a prio target -- see if it was a prio target
--local inList, gName = cfxReconMode.isStringInList(theGroup:getName(), cfxReconMode.prioList)
if inList then if inList then
-- if cfxReconMode.announcer then -- if cfxReconMode.announcer then
if cfxReconMode.verbose then if cfxReconMode.verbose then
@ -565,7 +567,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
-- increase prio flag -- increase prio flag
if cfxReconMode.prioFlag then if cfxReconMode.prioFlag then
cfxZones.pollFlag(cfxReconMode.prioFlag, cfxReconMode.method, cfxReconMode.theZone) cfxReconMode.theZone:pollFlag(cfxReconMode.prioFlag, cfxReconMode.method )
end end
-- see if we were passed additional info in zInfo -- see if we were passed additional info in zInfo
@ -583,7 +585,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
end end
if zInfo.theFlag then if zInfo.theFlag then
cfxZones.pollFlag(zInfo.theFlag, cfxReconMode.method, zInfo.theZone) zInfo.theZone:pollFlag(zInfo.theFlag, cfxReconMode.method)
if cfxReconMode.verbose or zInfo.theZone.verbose then if cfxReconMode.verbose or zInfo.theZone.verbose then
trigger.action.outText("+++rcn: banging <" .. zInfo.theFlag .. "> for prio target zone <" .. zInfo.theZone.name .. ">",30) trigger.action.outText("+++rcn: banging <" .. zInfo.theFlag .. "> for prio target zone <" .. zInfo.theZone.name .. ">",30)
end end
@ -595,7 +597,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
-- increase normal flag -- increase normal flag
if cfxReconMode.detectFlag then if cfxReconMode.detectFlag then
cfxZones.pollFlag(cfxReconMode.detectFlag, cfxReconMode.method, cfxReconMode.theZone) cfxReconMode.theZone:pollFlag(cfxReconMode.detectFlag, cfxReconMode.method)
end end
end end
end end
@ -655,7 +657,7 @@ function cfxReconMode.doDeActivate()
end end
end end
function cfxReconMode.updateQueues() function cfxReconSMode.updateQueues()
-- schedule next call -- schedule next call
timer.scheduleFunction(cfxReconMode.updateQueues, {}, timer.getTime() + 1/cfxReconMode.ups) timer.scheduleFunction(cfxReconMode.updateQueues, {}, timer.getTime() + 1/cfxReconMode.ups)
@ -681,7 +683,6 @@ function cfxReconMode.updateQueues()
-- the scouts array, move it to processed and then shrink -- the scouts array, move it to processed and then shrink
-- scouts table until it's empty. When empty, transfer all -- scouts table until it's empty. When empty, transfer all
-- back and start cycle anew -- back and start cycle anew
local theFocusScoutName = nil local theFocusScoutName = nil
local procCount = 0 -- no iterations done yet local procCount = 0 -- no iterations done yet
for name, scout in pairs(cfxReconMode.scouts) do for name, scout in pairs(cfxReconMode.scouts) do
@ -736,7 +737,6 @@ function cfxReconMode.autoRemove()
local toRemove = {} local toRemove = {}
-- scan all marked groups, and when they no longer exist, remove them -- scan all marked groups, and when they no longer exist, remove them
for idx, args in pairs (cfxReconMode.activeMarks) do for idx, args in pairs (cfxReconMode.activeMarks) do
-- args = {mySide, theScout, theGroup, theID, gName}
local gName = args[5] local gName = args[5]
if not cfxReconMode.isGroupStillAlive(gName) then if not cfxReconMode.isGroupStillAlive(gName) then
-- remove mark, remove group from set -- remove mark, remove group from set
@ -766,7 +766,7 @@ function cfxReconMode.lateEvalPlayerUnit(theUnit)
for idx, theZone in pairs (cfxReconMode.scoutZones) do for idx, theZone in pairs (cfxReconMode.scoutZones) do
local isScout = theZone.isScout local isScout = theZone.isScout
local dynamic = theZone.dynamic local dynamic = theZone.dynamic
local inZone = cfxZones.pointInZone(p, theZone) local inZone = theZone:pointInZone(p)
if inZone then if inZone then
if isScout then if isScout then
cfxReconMode.addToAllowedScoutList(aGroup, dynamic) cfxReconMode.addToAllowedScoutList(aGroup, dynamic)
@ -886,7 +886,6 @@ function cfxReconMode:onEvent(event)
end end
cfxReconMode.addScout(theUnit) cfxReconMode.addScout(theUnit)
end end
-- trigger.action.outText("+++rcn-onEvent: " .. event.id .. " for <" .. theUnit:getName() .. ">", 30)
end end
-- --
@ -984,68 +983,70 @@ function cfxReconMode.readConfigZone()
end end
end end
cfxReconMode.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) cfxReconMode.verbose = theZone.verbose --cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
cfxReconMode.autoRecon = cfxZones.getBoolFromZoneProperty(theZone, "autoRecon", true) cfxReconMode.autoRecon = theZone:getBoolFromZoneProperty("autoRecon", true)
cfxReconMode.redScouts = cfxZones.getBoolFromZoneProperty(theZone, "redScouts", false) cfxReconMode.redScouts = theZone:getBoolFromZoneProperty("redScouts", false)
cfxReconMode.blueScouts = cfxZones.getBoolFromZoneProperty(theZone, "blueScouts", true) cfxReconMode.blueScouts = theZone:getBoolFromZoneProperty( "blueScouts", true)
cfxReconMode.greyScouts = cfxZones.getBoolFromZoneProperty(theZone, "greyScouts", false) cfxReconMode.greyScouts = theZone:getBoolFromZoneProperty( "greyScouts", false)
cfxReconMode.playerOnlyRecon = cfxZones.getBoolFromZoneProperty(theZone, "playerOnlyRecon", false) cfxReconMode.playerOnlyRecon = theZone:getBoolFromZoneProperty("playerOnlyRecon", false)
cfxReconMode.reportNumbers = cfxZones.getBoolFromZoneProperty(theZone, "reportNumbers", true) cfxReconMode.reportNumbers = theZone:getBoolFromZoneProperty( "reportNumbers", true)
cfxReconMode.reportTime = cfxZones.getNumberFromZoneProperty(theZone, "reportTime", 30) cfxReconMode.reportTime = theZone:getNumberFromZoneProperty( "reportTime", 30)
cfxReconMode.detectionMinRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionMinRange", 3000) cfxReconMode.detectionMinRange = theZone:getNumberFromZoneProperty("detectionMinRange", 3000)
cfxReconMode.detectionMaxRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionMaxRange", 12000) cfxReconMode.detectionMaxRange = theZone:getNumberFromZoneProperty("detectionMaxRange", 12000)
cfxReconMode.maxAlt = cfxZones.getNumberFromZoneProperty(theZone, "maxAlt", 9000) cfxReconMode.maxAlt = theZone:getNumberFromZoneProperty("maxAlt", 9000)
if cfxZones.hasProperty(theZone, "prio+") then if theZone:hasProperty("prio+") then -- deprecated. remove next update
cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio+", "none") cfxReconMode.prioFlag = theZone:getStringFromZoneProperty("prio+", "none")
elseif cfxZones.hasProperty(theZone, "prio!") then elseif theZone:hasProperty("prio!") then
cfxReconMode.prioFlag = cfxZones.getStringFromZoneProperty(theZone, "prio!", "*<none>") cfxReconMode.prioFlag = theZone:getStringFromZoneProperty("prio!", "*<none>")
end end
if cfxZones.hasProperty(theZone, "detect+") then if theZone:hasProperty("detect+") then -- deprecated
cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect+", "none") cfxReconMode.detectFlag = theZone:getStringFromZoneProperty("detect+", "none")
elseif cfxZones.hasProperty(theZone, "detect!") then elseif theZone:hasProperty("detect!") then
cfxReconMode.detectFlag = cfxZones.getStringFromZoneProperty(theZone, "detect!", "*<none>") cfxReconMode.detectFlag = theZone:getStringFromZoneProperty("detect!", "*<none>")
end end
cfxReconMode.method = cfxZones.getStringFromZoneProperty(theZone, "method", "inc") cfxReconMode.method = theZone:getStringFromZoneProperty("method", "inc")
if cfxZones.hasProperty(theZone, "reconMethod") then if theZone:hasProperty("reconMethod") then
cfxReconMode.method = cfxZones.getStringFromZoneProperty(theZone, "reconMethod", "inc") cfxReconMode.method = theZone:getStringFromZoneProperty("reconMethod", "inc")
end end
cfxReconMode.applyMarks = cfxZones.getBoolFromZoneProperty(theZone, "applyMarks", true) cfxReconMode.applyMarks = theZone:getBoolFromZoneProperty( "applyMarks", true)
cfxReconMode.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true) cfxReconMode.marksFadeAfter = theZone:getNumberFromZoneProperty("marksFadeAfter", 30*60) -- 30 minutes default
-- trigger.action.outText("recon: announcer is " .. dcsCommon.bool2Text(cfxReconMode.announcer), 30) -- announced cfxReconMode.marksLocked = theZone:getBoolFromZoneProperty("marksLocked", false) -- if true, players cannot remove the marks
if cfxZones.hasProperty(theZone, "reconSound") then cfxReconMode.announcer = theZone:getBoolFromZoneProperty( "announcer", true)
cfxReconMode.reconSound = cfxZones.getStringFromZoneProperty(theZone, "reconSound", "<nosound>")
if theZone:hasProperty("reconSound") then
cfxReconMode.reconSound = theZone:getStringFromZoneProperty("reconSound", "<nosound>")
end end
cfxReconMode.removeWhenDestroyed = cfxZones.getBoolFromZoneProperty(theZone, "autoRemove", true) cfxReconMode.removeWhenDestroyed = theZone:getBoolFromZoneProperty("autoRemove", true)
cfxReconMode.mgrs = cfxZones.getBoolFromZoneProperty(theZone, "mgrs", false) cfxReconMode.mgrs = theZone:getBoolFromZoneProperty("mgrs", false)
cfxReconMode.active = cfxZones.getBoolFromZoneProperty(theZone, "active", true) cfxReconMode.active = theZone:getBoolFromZoneProperty("active", true)
if cfxZones.hasProperty(theZone, "activate?") then if theZone:hasProperty("activate?") then
cfxReconMode.activate = cfxZones.getStringFromZoneProperty(theZone, "activate?", "*<none>") cfxReconMode.activate = theZone:getStringFromZoneProperty("activate?", "*<none>")
cfxReconMode.lastActivate = cfxZones.getFlagValue(cfxReconMode.activate, theZone) cfxReconMode.lastActivate = theZone:getFlagValue(cfxReconMode.activate)
elseif cfxZones.hasProperty(theZone, "on?") then elseif theZone:hasProperty("on?") then
cfxReconMode.activate = cfxZones.getStringFromZoneProperty(theZone, "on?", "*<none>") cfxReconMode.activate = theZone:getStringFromZoneProperty("on?", "*<none>")
cfxReconMode.lastActivate = cfxZones.getFlagValue(cfxReconMode.activate, theZone) cfxReconMode.lastActivate = theZone:getFlagValue(cfxReconMode.activate)
end end
if cfxZones.hasProperty(theZone, "deactivate?") then if theZone:hasProperty("deactivate?") then
cfxReconMode.deactivate = cfxZones.getStringFromZoneProperty(theZone, "deactivate?", "*<none>") cfxReconMode.deactivate = theZone:getStringFromZoneProperty("deactivate?", "*<none>")
cfxReconMode.lastDeActivate = cfxZones.getFlagValue(cfxReconMode.deactivate, theZone) cfxReconMode.lastDeActivate = theZone:getFlagValue(cfxReconMode.deactivate)
elseif cfxZones.hasProperty(theZone, "off?") then elseif theZone:hasProperty("off?") then
cfxReconMode.deactivate = cfxZones.getStringFromZoneProperty(theZone, "off?", "*<none>") cfxReconMode.deactivate = theZone:getStringFromZoneProperty("off?", "*<none>")
cfxReconMode.lastDeActivate = cfxZones.getFlagValue(cfxReconMode.deactivate, theZone) cfxReconMode.lastDeActivate = theZone:getFlagValue(cfxReconMode.deactivate)
end end
cfxReconMode.imperialUnits = cfxZones.getBoolFromZoneProperty(theZone, "imperial", false) cfxReconMode.imperialUnits = theZone:getBoolFromZoneProperty("imperial", false)
if cfxZones.hasProperty(theZone, "imperialUnits") then if theZone:hasProperty("imperialUnits") then
cfxReconMode.imperialUnits = cfxZones.getBoolFromZoneProperty(theZone, "imperialUnits", false) cfxReconMode.imperialUnits = theZone:getBoolFromZoneProperty( "imperialUnits", false)
end end
cfxReconMode.theZone = theZone -- save this zone cfxReconMode.theZone = theZone -- save this zone
@ -1057,24 +1058,24 @@ end
function cfxReconMode.processReconZone(theZone) function cfxReconMode.processReconZone(theZone)
local theList = cfxZones.getStringFromZoneProperty(theZone, "recon", "prio") local theList = theZone:getStringFromZoneProperty("recon", "prio")
theList = string.upper(theList) theList = string.upper(theList)
local isBlack = dcsCommon.stringStartsWith(theList, "BLACK") local isBlack = dcsCommon.stringStartsWith(theList, "BLACK")
local zInfo = {} local zInfo = {}
zInfo.theZone = theZone zInfo.theZone = theZone
zInfo.isBlack = isBlack zInfo.isBlack = isBlack
zInfo.silent = cfxZones.getBoolFromZoneProperty(theZone, "silent", false) zInfo.silent = theZone:getBoolFromZoneProperty("silent", false)
if cfxZones.hasProperty(theZone, "spotted!") then if theZone:hasProperty("spotted!") then
zInfo.theFlag = cfxZones.getStringFromZoneProperty(theZone, "spotted!", "*<none>") zInfo.theFlag = theZone:getStringFromZoneProperty("spotted!", "*<none>")
end end
if cfxZones.hasProperty(theZone, "prioMessage") then if theZone:hasProperty("prioMessage") then
zInfo.prioMessage = cfxZones.getStringFromZoneProperty(theZone, "prioMessage", "<none>") zInfo.prioMessage = theZone:getStringFromZoneProperty("prioMessage", "<none>")
end end
local dynamic = cfxZones.getBoolFromZoneProperty(theZone, "dynamic", false) local dynamic = theZone:getBoolFromZoneProperty("dynamic", false)
zInfo.dynamic = dynamic zInfo.dynamic = dynamic
local categ = 2 -- ground troops only local categ = 2 -- ground troops only
local allGroups = cfxZones.allGroupsInZone(theZone, categ) local allGroups = cfxZones.allGroupsInZone(theZone, categ)
@ -1100,15 +1101,15 @@ function cfxReconMode.processReconZone(theZone)
end end
function cfxReconMode.processScoutZone(theZone) function cfxReconMode.processScoutZone(theZone)
local isScout = cfxZones.getBoolFromZoneProperty(theZone, "scout", true) local isScout = theZone:getBoolFromZoneProperty("scout", true)
local dynamic = cfxZones.getBoolFromZoneProperty(theZone, "dynamic") local dynamic = theZone:getBoolFromZoneProperty("dynamic")
theZone.dynamic = dynamic theZone.dynamic = dynamic
theZone.isScout = isScout theZone.isScout = isScout
local categ = 0 -- aircraft local categ = 0 -- aircraft
local allFixed = cfxZones.allGroupsInZone(theZone, categ) local allFixed = theZone:allGroupsInZone(categ)
local categ = 1 -- helos local categ = 1 -- helos
local allRotor = cfxZones.allGroupsInZone(theZone, categ) local allRotor = theZone:allGroupsInZone(categ)
local allGroups = dcsCommon.combineTables(allFixed, allRotor) local allGroups = dcsCommon.combineTables(allFixed, allRotor)
for idx, aGroup in pairs(allGroups) do for idx, aGroup in pairs(allGroups) do
if isScout then if isScout then

View File

@ -149,6 +149,7 @@ cfxZones.version = "4.0.0"
- getNumberFromZoneProperty() enforces number return even on default - getNumberFromZoneProperty() enforces number return even on default
- immediate method switched to preceeding '#', to resolve conflict witzh - immediate method switched to preceeding '#', to resolve conflict witzh
negative numbers, backwards compatibility with old (dysfunctional) method negative numbers, backwards compatibility with old (dysfunctional) method
- 4.0.1 - dmlZone:getName()
--]]-- --]]--
-- --
@ -3258,6 +3259,10 @@ function dmlZone:getPoint(getHeight)
return thePos return thePos
end end
function dmlZone:getName() -- no cfxZones.bridge!
return self.name
end
function cfxZones.linkUnitToZone(theUnit, theZone, dx, dy) -- note: dy is really Z, don't get confused!!!! function cfxZones.linkUnitToZone(theUnit, theZone, dx, dy) -- note: dy is really Z, don't get confused!!!!
theZone.linkedUnit = theUnit theZone.linkedUnit = theUnit
if not dx then dx = 0 end if not dx then dx = 0 end

View File

@ -1,5 +1,5 @@
civAir = {} civAir = {}
civAir.version = "1.5.2" civAir.version = "2.0.0"
--[[-- --[[--
1.0.0 initial version 1.0.0 initial version
1.1.0 exclude list for airfields 1.1.0 exclude list for airfields
@ -23,7 +23,15 @@ civAir.version = "1.5.2"
exclude list and include list exclude list and include list
1.5.1 added depart only and arrive only options for airfields 1.5.1 added depart only and arrive only options for airfields
1.5.2 fixed bugs inb verbosity 1.5.2 fixed bugs inb verbosity
2.0.0 dmlZones
inbound zones
outbound zones
on start location is randomizes 30-70% of the way
guarded 'no longer exist' warning for verbosity
changed unit naming from -civA to -GA
strenghtened guard on testing against free slots for other units
flights are now of random neutral countries
maxFlights synonym for maxTraffic
--]]-- --]]--
@ -48,22 +56,22 @@ civAir.trafficCenters = {}
-- If the attribute's value is anything -- If the attribute's value is anything
-- but "exclude", the closest airfield to the zone -- but "exclude", the closest airfield to the zone
-- is added to trafficCenters -- is added to trafficCenters
-- if you leave this list empty, and do not add airfields -- if you leave this list empty, and do not add airfields
-- by zones, the list is automatically populated with all -- by zones, the list is automatically populated with all
-- airfields in the map -- airfields in the map
-- if name starts with "***" then it is not an airfield, but zone
civAir.excludeAirfields = {} civAir.excludeAirfields = {}
-- list all airfields that must NOT be included in -- list all airfields that must NOT be included in
-- civilian activities. Will be used for neither landing -- civilian activities. Will be used for neither landing
-- nor departure. overrides any airfield that was included -- nor departure. overrides any airfield that was included
-- in trafficCenters. Here, Senaki is off limits for -- in trafficCenters.
-- civilian air traffic
-- can be populated by zone on the map that have the -- can be populated by zone on the map that have the
-- 'civAir' attribute with value "exclude" -- 'civAir' attribute with value "exclude"
civAir.departOnly = {} -- use only to start from civAir.departOnly = {} -- use only to start from
civAir.landingOnly = {} -- use only to land at civAir.landingOnly = {} -- use only to land at
civAir.inoutZones = {} -- off-map connector zones
civAir.requiredLibs = { civAir.requiredLibs = {
"dcsCommon", -- common is of course needed for everything "dcsCommon", -- common is of course needed for everything
@ -72,57 +80,73 @@ civAir.requiredLibs = {
civAir.activePlanes = {} civAir.activePlanes = {}
civAir.idlePlanes = {} civAir.idlePlanes = {}
civAir.outboundFlights = {} -- only flights that are enroute to an outbound zone
function civAir.readConfigZone() function civAir.readConfigZone()
-- note: must match exactly!!!! -- note: must match exactly!!!!
local theZone = cfxZones.getZoneByName("civAirConfig") local theZone = cfxZones.getZoneByName("civAirConfig")
if not theZone then if not theZone then
trigger.action.outText("***civA: NO config zone!", 30) trigger.action.outText("***civA: NO config zone!", 30)
return theZone = cfxZones.createSimpleZone("civAirConfig")
end end
trigger.action.outText("civA: found config zone!", 30)
-- ok, for each property, load it if it exists -- ok, for each property, load it if it exists
if cfxZones.hasProperty(theZone, "aircraftTypes") then if theZone:hasProperty("aircraftTypes") then
local theTypes = cfxZones.getStringFromZoneProperty(theZone, "aircraftTypes", "Yak-40") local theTypes = theZone:getStringFromZoneProperty( "aircraftTypes", civAir.aircraftTypes) -- "Yak-40")
local typeArray = dcsCommon.splitString(theTypes, ",") local typeArray = dcsCommon.splitString(theTypes, ",")
typeArray = dcsCommon.trimArray(typeArray) typeArray = dcsCommon.trimArray(typeArray)
civAir.aircraftTypes = typeArray civAir.aircraftTypes = typeArray
end end
if cfxZones.hasProperty(theZone, "ups") then if theZone:hasProperty("ups") then
civAir.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 0.05) civAir.ups = theZone:getNumberFromZoneProperty("ups", 0.05)
if civAir.ups < .0001 then civAir.ups = 0.05 end if civAir.ups < .0001 then civAir.ups = 0.05 end
end end
if cfxZones.hasProperty(theZone, "maxTraffic") then if theZone:hasProperty("maxTraffic") then
civAir.maxTraffic = cfxZones.getNumberFromZoneProperty(theZone, "maxTraffic", 10) civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxTraffic", 10)
elseif theZone:hasProperty("maxFlights") then
civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxFlights", 10)
end end
if cfxZones.hasProperty(theZone, "maxIdle") then if theZone:hasProperty("maxIdle") then
civAir.maxIdle = cfxZones.getNumberFromZoneProperty(theZone, "maxIdle", 8 * 60) civAir.maxIdle = theZone:getNumberFromZoneProperty("maxIdle", 8 * 60)
end end
if cfxZones.hasProperty(theZone, "initialAirSpawns") then if theZone:hasProperty("initialAirSpawns") then
civAir.initialAirSpawns = cfxZones.getBoolFromZoneProperty(theZone, "initialAirSpawns", true) civAir.initialAirSpawns = theZone:getBoolFromZoneProperty( "initialAirSpawns", true)
end end
civAir.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) civAir.verbose = theZone.verbose -- cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
end end
function civAir.processZone(theZone) function civAir.processZone(theZone)
local value = cfxZones.getStringFromZoneProperty(theZone, "civAir", "") local value = theZone:getStringFromZoneProperty("civAir", "")
local af = dcsCommon.getClosestAirbaseTo(theZone.point, 0) -- 0 = only airfields, not farp or ships local af = dcsCommon.getClosestAirbaseTo(theZone.point, 0) -- 0 = only airfields, not farp or ships
local inoutName = "***" .. theZone:getName()
if af then if af then
local afName = af:getName() local afName = af:getName()
value = value:lower() value = value:lower()
if value == "exclude" then if value == "exclude" or value == "closed" then
table.insert(civAir.excludeAirfields, afName) table.insert(civAir.excludeAirfields, afName)
elseif dcsCommon.stringStartsWith(value, "depart") or dcsCommon.stringStartsWith(value, "start") then elseif dcsCommon.stringStartsWith(value, "depart") or dcsCommon.stringStartsWith(value, "start") or dcsCommon.stringStartsWith(value, "take") then
table.insert(civAir.departOnly, afName) table.insert(civAir.departOnly, afName)
elseif dcsCommon.stringStartsWith(value, "land") or dcsCommon.stringStartsWith(value, "arriv") then elseif dcsCommon.stringStartsWith(value, "land") or dcsCommon.stringStartsWith(value, "arriv") then
table.insert(civAir.landingOnly, afName) table.insert(civAir.landingOnly, afName)
elseif dcsCommon.stringStartsWith(value, "inb") then
table.insert(civAir.departOnly, inoutName) -- start in inbound zone
civAir.inoutZones[inoutName] = theZone
-- theZone.inbound = true
elseif dcsCommon.stringStartsWith(value, "outb") then
table.insert(civAir.landingOnly, inoutName)
civAir.inoutZones[inoutName] = theZone
-- theZone.outbound = true
elseif dcsCommon.stringStartsWith(value, "in/out") then
table.insert(civAir.trafficCenters, inoutName)
civAir.inoutZones[inoutName] = theZone
-- theZone.inbound = true
-- theZone.outbound = true
else else
table.insert(civAir.trafficCenters, afName) -- note that adding the same twice makes it more likely to be picked table.insert(civAir.trafficCenters, afName) -- note that adding the same twice makes it more likely to be picked
end end
@ -142,11 +166,12 @@ function civAir.removePlaneGroupByName(aName)
return return
end end
if civAir.activePlanes[aName] then if civAir.activePlanes[aName] then
--trigger.action.outText("civA: REMOVING " .. aName .. " ***", 30)
civAir.activePlanes[aName] = nil civAir.activePlanes[aName] = nil
else else
if civAir.verbose then
trigger.action.outText("civA: warning - ".. aName .." remove req but not found", 30) trigger.action.outText("civA: warning - ".. aName .." remove req but not found", 30)
end end
end
end end
function civAir.removePlane(thePlaneUnit) -- warning: is actually a group function civAir.removePlane(thePlaneUnit) -- warning: is actually a group
@ -207,10 +232,25 @@ function civAir.getTwoAirbases()
tries = tries + 1 -- only try 10 times tries = tries + 1 -- only try 10 times
until fAB ~= sAB or tries > 10 until fAB ~= sAB or tries > 10
fAB = dcsCommon.getFirstAirbaseWhoseNameContains(fAB, 0)
sAB = dcsCommon.getFirstAirbaseWhoseNameContains(sAB, 0)
return fAB, sAB local civA = {}
if not (dcsCommon.stringStartsWith(fAB, '***')) then
civA.AB = dcsCommon.getFirstAirbaseWhoseNameContains(fAB, 0)
civA.name = civA.AB:getName()
else
civA.zone = civAir.inoutZones[fAB]
civA.name = civA.zone:getName()
end
local civB = {}
if not (dcsCommon.stringStartsWith(sAB, '***')) then
civB.AB = dcsCommon.getFirstAirbaseWhoseNameContains(sAB, 0)
civB.name = civB.AB:getName()
else
civB.zone = civAir.inoutZones[sAB]
civB.name = civB.zone:getName()
end
return civA, civB -- fAB, sAB
end end
function civAir.parkingIsFree(fromWP) function civAir.parkingIsFree(fromWP)
@ -222,9 +262,9 @@ function civAir.parkingIsFree(fromWP)
loc.z = fromWP.z loc.z = fromWP.z
for name, aPlaneGroup in pairs(civAir.activePlanes) do for name, aPlaneGroup in pairs(civAir.activePlanes) do
if aPlaneGroup:isExist() then if Group.isExist(aPlaneGroup) then
local aPlane = aPlaneGroup:getUnit(1) local aPlane = aPlaneGroup:getUnit(1)
if aPlane:isExist() then if aPlane and Unit.isExist(aPlane) then
pos = aPlane:getPoint() pos = aPlane:getPoint()
local delta = dcsCommon.dist(loc, pos) local delta = dcsCommon.dist(loc, pos)
if delta < 21 then if delta < 21 then
@ -242,21 +282,35 @@ end
civAir.airStartSeparation = 0 civAir.airStartSeparation = 0
function civAir.createFlight(name, theTypeString, fromAirfield, toAirfield, inAirStart) function civAir.createFlight(name, theTypeString, fromAirfield, toAirfield, inAirStart)
if not fromAirfield then if not fromAirfield then
trigger.action.outText("civA: NIL fromAirfield", 30) trigger.action.outText("civA: NIL source", 30)
return nil return nil
end end
if not toAirfield then if not toAirfield then
trigger.action.outText("civA: NIL toAirfield", 30) trigger.action.outText("civA: NIL destination", 30)
return nil return nil
end end
local randomizeLoc = inAirStart
local theGroup = dcsCommon.createEmptyAircraftGroupData (name) local theGroup = dcsCommon.createEmptyAircraftGroupData (name)
local theAUnit = dcsCommon.createAircraftUnitData(name .. "-civA", theTypeString, false) local theAUnit = dcsCommon.createAircraftUnitData(name .. "-GA", theTypeString, false)
theAUnit.payload.fuel = 100000 theAUnit.payload.fuel = 100000
dcsCommon.addUnitToGroupData(theAUnit, theGroup) dcsCommon.addUnitToGroupData(theAUnit, theGroup)
local fromWP = dcsCommon.createTakeOffFromParkingRoutePointData(fromAirfield) local fromWP
if fromAirfield.AB then
fromWP = dcsCommon.createTakeOffFromParkingRoutePointData(fromAirfield.AB)
else
-- we start in air from inside inbound zone
local p = fromAirfield.zone:createRandomPointInZone()
local alt = fromAirfield.zone:getNumberFromZoneProperty("alt", 8000)
fromWP = dcsCommon.createSimpleRoutePointData(p, alt)
theAUnit.alt = fromWP.alt
theAUnit.speed = fromWP.speed
inAirStart = false -- it already is, no separation shenigans
end
if not fromWP then if not fromWP then
trigger.action.outText("civA: fromWP create failed", 30) trigger.action.outText("civA: fromWP create failed", 30)
return nil return nil
@ -273,11 +327,22 @@ function civAir.createFlight(name, theTypeString, fromAirfield, toAirfield, inAi
theAUnit.alt = fromWP.alt theAUnit.alt = fromWP.alt
theAUnit.speed = fromWP.speed theAUnit.speed = fromWP.speed
end end
-- now look at destination: airfield or zone?
local zoneApproach = toAirfield.zone
local toWP
local overheadWP
if zoneApproach then
-- we fly this plane to a zone, and then disappear it
local p = zoneApproach:getPoint()
local alt = zoneApproach:getNumberFromZoneProperty("alt", 8000)
toWP = dcsCommon.createSimpleRoutePointData(p, alt)
else
-- sometimes, when landing kicks in too early, the plane lands -- sometimes, when landing kicks in too early, the plane lands
-- at the wrong airfield. AI sucks. -- at the wrong airfield. AI sucks.
-- so we force overflight of target airfield -- so we force overflight of target airfield
local overheadWP = dcsCommon.createOverheadAirdromeRoutPintData(toAirfield) overheadWP = dcsCommon.createOverheadAirdromeRoutPintData(toAirfield.AB)
local toWP = dcsCommon.createLandAtAerodromeRoutePointData(toAirfield) toWP = dcsCommon.createLandAtAerodromeRoutePointData(toAirfield.AB)
if not toWP then if not toWP then
trigger.action.outText("civA: toWP create failed", 30) trigger.action.outText("civA: toWP create failed", 30)
return nil return nil
@ -287,17 +352,49 @@ function civAir.createFlight(name, theTypeString, fromAirfield, toAirfield, inAi
trigger.action.outText("civA: failed free parking check for flight " .. name, 30) trigger.action.outText("civA: failed free parking check for flight " .. name, 30)
return nil return nil
end end
end
if randomizeLoc then
-- make first wp to somewhere 30-70 towards toWP
local percent = (math.random(40) + 30) / 100
local mx = dcsCommon.lerp(fromWP.x, toWP.x, percent)
local my = dcsCommon.lerp(fromWP.y, toWP.y, percent)
fromWP.x = mx
fromWP.y = my
fromWP.speed = 150
fromWP.alt = 8000
theAUnit.alt = fromWP.alt
theAUnit.speed = fromWP.speed
end
if (not fromAirfield.AB) or randomizedLoc or inAirStart then
-- set current heading correct towards toWP
local hdg = dcsCommon.bearingFromAtoBusingXY(fromWP, toWP)
theAUnit.heading = hdg
theAUnit.psi = -hdg
end
dcsCommon.moveGroupDataTo(theGroup, dcsCommon.moveGroupDataTo(theGroup,
fromWP.x, fromWP.x,
fromWP.y) fromWP.y)
dcsCommon.addRoutePointForGroupData(theGroup, fromWP) dcsCommon.addRoutePointForGroupData(theGroup, fromWP)
if not zoneApproach then
dcsCommon.addRoutePointForGroupData(theGroup, overheadWP) dcsCommon.addRoutePointForGroupData(theGroup, overheadWP)
end
dcsCommon.addRoutePointForGroupData(theGroup, toWP) dcsCommon.addRoutePointForGroupData(theGroup, toWP)
-- spawn -- spawn
local groupCat = Group.Category.AIRPLANE local groupCat = Group.Category.AIRPLANE
local theSpawnedGroup = coalition.addGroup(82, groupCat, theGroup) -- 82 is UN peacekeepers local allNeutral = dcsCommon.getCountriesForCoalition(0)
local aRandomNeutral = dcsCommon.pickRandom(allNeutral)
if not aRandomNeutral then
trigger.action.outText("+++civA: WARNING: no neutral countries exist, flight is not neutral.", 30)
end
local theSpawnedGroup = coalition.addGroup(aRandomNeutral, groupCat, theGroup) -- 82 is UN peacekeepers
if zoneApproach then
-- track this flight to target zone
civAir.outboundFlights[name] = zoneApproach
end
return theSpawnedGroup return theSpawnedGroup
end end
@ -312,7 +409,9 @@ function civAir.createNewFlight(inAirStart)
return return
end end
local name = fAB:getName() .. "-" .. sAB:getName().. "/" .. civAir.flightCount -- fAB and sAB are tables that have either .base or AB set
local name = fAB.name .. "-" .. sAB.name.. "/" .. civAir.flightCount
local TypeString = dcsCommon.pickRandom(civAir.aircraftTypes) local TypeString = dcsCommon.pickRandom(civAir.aircraftTypes)
local theFlight = civAir.createFlight(name, TypeString, fAB, sAB, inAirStart) local theFlight = civAir.createFlight(name, TypeString, fAB, sAB, inAirStart)
@ -325,7 +424,7 @@ function civAir.createNewFlight(inAirStart)
civAir.addPlane(theFlight) -- track it civAir.addPlane(theFlight) -- track it
if civAir.verbose then if civAir.verbose then
trigger.action.outText("civA: created flight from <" .. fAB:getName() .. "> to <" .. sAB:getName() .. ">", 30) trigger.action.outText("civA: created flight from <" .. fAB.name .. "> to <" .. sAB.name .. ">", 30)
end end
end end
@ -340,9 +439,40 @@ function civAir.airStartPopulation()
end end
-- --
-- U P D A T E L O O P -- U P D A T E L O O P S
-- --
function civAir.trackOutbound()
timer.scheduleFunction(civAir.trackOutbound, {}, timer.getTime() + 10)
-- iterate all flights that are outbound
local filtered = {}
for gName, theZone in pairs(civAir.outboundFlights) do
local theGroup = Group.getByName(gName)
if theGroup then
local theUnit = theGroup:getUnit(1)
if theUnit and Unit.isExist(theUnit) then
local p = theUnit:getPoint()
local t = theZone:getPoint()
local d = dcsCommon.distFlat(p, t)
if d > 3000 then -- works unless plane faster than 300m/s = 1080 km/h
-- keep watching
filtered[gName] = theZone
else
-- we can disappear the group
if civAir.verbose then
trigger.action.outText("+++civA: flight <" .. gName .. "> has reached map outbound zone <" .. theZone:getName() .. "> and is removed", 30)
end
Group.destroy(theGroup)
end
else
trigger.action.outText("+++civ: lost unit in group <" .. gName .. "> heading for <" .. theZone:getName() .. ">", 30)
end
end
end
civAir.outboundFlights = filtered
end
function civAir.update() function civAir.update()
-- reschedule me in the future. ups = updates per second. -- reschedule me in the future. ups = updates per second.
timer.scheduleFunction(civAir.update, {}, timer.getTime() + 1/civAir.ups) timer.scheduleFunction(civAir.update, {}, timer.getTime() + 1/civAir.ups)
@ -359,12 +489,15 @@ function civAir.update()
for idx, name in pairs(removeMe) do for idx, name in pairs(removeMe) do
civAir.activePlanes[name] = nil civAir.activePlanes[name] = nil
trigger.action.outText("civA: warning - removed " .. name .. " from active roster, no longer exists", 30) if civAir.verbose then
trigger.action.outText("civA: removed " .. name .. " from active roster, no longer exists", 30)
end
end end
-- now, run through all existing flights and update their -- now, run through all existing flights and update their
-- idle times. also count how many planes there are -- idle times. also count how many planes there are
-- so we can respawn if we are below max
local planeNum = 0 local planeNum = 0
local overduePlanes = {} local overduePlanes = {}
local now = timer.getTime() local now = timer.getTime()
@ -372,18 +505,17 @@ function civAir.update()
local speed = 0 local speed = 0
if aPlaneGroup:isExist() then if aPlaneGroup:isExist() then
local aPlane = aPlaneGroup:getUnit(1) local aPlane = aPlaneGroup:getUnit(1)
if aPlane and Unit.isExist(aPlane) and aPlane:getLife() >= 1 then
if aPlane and aPlane:isExist() and aPlane:getLife() >= 1 then
planeNum = planeNum + 1 planeNum = planeNum + 1
local vel = aPlane:getVelocity() local vel = aPlane:getVelocity()
speed = dcsCommon.mag(vel.x, vel.y, vel.z) speed = dcsCommon.mag(vel.x, vel.y, vel.z)
else else
-- force removal of group -- force removal of group, plane no longer exists
civAir.idlePlanes[name] = -1000 civAir.idlePlanes[name] = -1000
speed = 0 speed = 0
end end
else else
-- force removal -- force removal, group no longer exists
civAir.idlePlanes[name] = -1000 civAir.idlePlanes[name] = -1000
speed = 0 speed = 0
end end
@ -398,10 +530,9 @@ function civAir.update()
table.insert(overduePlanes, name) table.insert(overduePlanes, name)
end end
else else
-- zero out idle plane -- zero out idle plane, it's moving fast enough
civAir.idlePlanes[name] = nil civAir.idlePlanes[name] = nil
end end
--]]--
end end
-- see if we have less than max flights running -- see if we have less than max flights running
@ -414,9 +545,12 @@ function civAir.update()
for idx, aName in pairs(overduePlanes) do for idx, aName in pairs(overduePlanes) do
local aFlight = civAir.getPlane(aName) -- returns a group local aFlight = civAir.getPlane(aName) -- returns a group
civAir.removePlaneGroupByName(aName) -- remove from roster civAir.removePlaneGroupByName(aName) -- remove from roster
if aFlight and aFlight:isExist() then if aFlight and Unit.isExist(aFlight) then
-- destroy can only work if group isexist! -- destroy can only work if group isexist!
Group.destroy(aFlight) -- remember: flights are groups! Group.destroy(aFlight) -- remember: flights are groups!
if civAir.verbose then
trigger.action.outText("+++civA: removed flight <" .. aName .. "> for overtime.", 30)
end
end end
end end
end end
@ -504,6 +638,8 @@ function civAir.start()
-- start the update loop -- start the update loop
civAir.update() civAir.update()
-- start outbound tracking
civAir.trackOutbound()
-- say hi! -- say hi!
trigger.action.outText("cf/x civAir v" .. civAir.version .. " started.", 30) trigger.action.outText("cf/x civAir v" .. civAir.version .. " started.", 30)
@ -518,10 +654,11 @@ end
--[[-- --[[--
Additional ideas Additional ideas
- border zones: ac can airstart in there and disappear in there
- callbacks for civ spawn / despawn - callbacks for civ spawn / despawn
- add civkill callback / redCivKill blueCivKill flag bangers - add civkill callback / redCivKill blueCivKill flag bangers
- Helicopter support - Helicopter support
- departure only, destination only
- add slot checking to see if other planes block it even though DCS claims the slot is free - add slot checking to see if other planes block it even though DCS claims the slot is free
- allow list of countries to choose civ air from
- ability to force a flight from a source? How do we make a destination? currently not a good idea
--]]-- --]]--

View File

@ -1,5 +1,5 @@
dcsCommon = {} dcsCommon = {}
dcsCommon.version = "2.9.0" dcsCommon.version = "2.9.2"
--[[-- VERSION HISTORY --[[-- VERSION HISTORY
2.2.6 - compassPositionOfARelativeToB 2.2.6 - compassPositionOfARelativeToB
- clockPositionOfARelativeToB - clockPositionOfARelativeToB
@ -166,7 +166,12 @@ dcsCommon.version = "2.9.0"
2.9.0 - createPoint() moved from cfxZones 2.9.0 - createPoint() moved from cfxZones
- copyPoint() moved from cfxZones - copyPoint() moved from cfxZones
- numberArrayFromString() moved from cfxZones - numberArrayFromString() moved from cfxZones
2.9.1 - new createSimpleRoutePointData()
- createOverheadAirdromeRoutPintData corrected and legacy support added
- new bearingFromAtoBusingXY()
- corrected verbosity for bearingFromAtoB
- new getCountriesForCoalition()
2.9.2 - updated task2text
--]]-- --]]--
@ -182,6 +187,7 @@ dcsCommon.version = "2.9.0"
dcsCommon.cbID = 0 -- callback id for simple callback scheduling dcsCommon.cbID = 0 -- callback id for simple callback scheduling
dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P"} -- Ka-50, Apache and Gazelle can't carry troops dcsCommon.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P"} -- Ka-50, Apache and Gazelle can't carry troops
dcsCommon.coalitionSides = {0, 1, 2} dcsCommon.coalitionSides = {0, 1, 2}
dcsCommon.maxCountry = 86 -- number of countries defined in total
-- lookup tables -- lookup tables
dcsCommon.groupID2Name = {} dcsCommon.groupID2Name = {}
@ -747,23 +753,23 @@ dcsCommon.version = "2.9.0"
return 0 return 0
end end
if not B then if not B then
trigger.action.outText("WARNING: no 'A' in bearingFromAtoB", 30) trigger.action.outText("WARNING: no 'B' in bearingFromAtoB", 30)
return 0 return 0
end end
if not A.x then if not A.x then
trigger.action.outText("WARNING: no 'A.x' (type A =<" .. type(A) .. ">)in bearingFromAtoB", 30) trigger.action.outText("WARNING: no 'A.x' (type A =<" .. type(A) .. ">)in bearingFromAtoB", 30)
return 0 return 0
end end
if not A.y then if not A.z then
trigger.action.outText("WARNING: no 'A.x' (type A =<" .. type(A) .. ">)in bearingFromAtoB", 30) trigger.action.outText("WARNING: no 'A.z' (type A =<" .. type(A) .. ">)in bearingFromAtoB", 30)
return 0 return 0
end end
if not B.x then if not B.x then
trigger.action.outText("WARNING: no 'B.x' (type B =<" .. type(B) .. ">)in bearingFromAtoB", 30) trigger.action.outText("WARNING: no 'B.x' (type B =<" .. type(B) .. ">)in bearingFromAtoB", 30)
return 0 return 0
end end
if not B.y then if not B.z then
trigger.action.outText("WARNING: no 'B.y' (type B =<" .. type(B) .. ">)in bearingFromAtoB", 30) trigger.action.outText("WARNING: no 'B.z' (type B =<" .. type(B) .. ">)in bearingFromAtoB", 30)
return 0 return 0
end end
@ -773,6 +779,38 @@ dcsCommon.version = "2.9.0"
return bearing return bearing
end end
function dcsCommon.bearingFromAtoBusingXY(A, B) -- coords in x, y
if not A then
trigger.action.outText("WARNING: no 'A' in bearingFromAtoBXY", 30)
return 0
end
if not B then
trigger.action.outText("WARNING: no 'B' in bearingFromAtoBXY", 30)
return 0
end
if not A.x then
trigger.action.outText("WARNING: no 'A.x' (type A =<" .. type(A) .. ">)in bearingFromAtoBXY", 30)
return 0
end
if not A.y then
trigger.action.outText("WARNING: no 'A.y' (type A =<" .. type(A) .. ">)in bearingFromAtoBXY", 30)
return 0
end
if not B.x then
trigger.action.outText("WARNING: no 'B.x' (type B =<" .. type(B) .. ">)in bearingFromAtoBXY", 30)
return 0
end
if not B.y then
trigger.action.outText("WARNING: no 'B.y' (type B =<" .. type(B) .. ">)in bearingFromAtoBXY", 30)
return 0
end
local dx = B.x - A.x
local dz = B.y - A.y
local bearing = math.atan2(dz, dx) -- in radiants
return bearing
end
function dcsCommon.bearingInDegreesFromAtoB(A, B) function dcsCommon.bearingInDegreesFromAtoB(A, B)
local bearing = dcsCommon.bearingFromAtoB(A, B) -- in rads local bearing = dcsCommon.bearingFromAtoB(A, B) -- in rads
bearing = math.floor(bearing / math.pi * 180) bearing = math.floor(bearing / math.pi * 180)
@ -1116,7 +1154,7 @@ dcsCommon.version = "2.9.0"
-- coalition's countries -- coalition's countries
-- we start with id=0 (Russia), go to id=85 (Slovenia), but skip id = 14 -- we start with id=0 (Russia), go to id=85 (Slovenia), but skip id = 14
local i = 0 local i = 0
while i < 86 do while i < dcsCommon.maxCountry do -- 86 do
if i ~= 14 then if i ~= 14 then
if (coalition.getCountryCoalition(i) == aCoalition) then return i end if (coalition.getCountryCoalition(i) == aCoalition) then return i end
end end
@ -1125,6 +1163,22 @@ dcsCommon.version = "2.9.0"
return nil return nil
end end
function dcsCommon.getCountriesForCoalition(aCoalition)
if not aCoalition then aCoalition = 0 end
local allCty = {}
local i = 0
while i < dcsCommon.maxCountry do
if i ~= 14 then -- there is no county 14
if (coalition.getCountryCoalition(i) == aCoalition) then
table.insert(allCty, i)
end
end
i = i + 1
end
return allCty
end
-- --
-- --
-- C A L L B A C K H A N D L E R -- C A L L B A C K H A N D L E R
@ -1394,7 +1448,7 @@ dcsCommon.version = "2.9.0"
return rp return rp
end end
function dcsCommon.createOverheadAirdromeRoutPintData(aerodrome) function dcsCommon.createOverheadAirdromeRoutePointData(aerodrome)
if not aerodrome then return nil end if not aerodrome then return nil end
local rp = {} local rp = {}
local p = aerodrome:getPoint() local p = aerodrome:getPoint()
@ -1408,6 +1462,9 @@ dcsCommon.version = "2.9.0"
rp.alt_type = "BARO" rp.alt_type = "BARO"
return rp return rp
end end
function dcsCommon.createOverheadAirdromeRoutPintData(aerodrome) -- backwards-compat to typo
return dcsCommon.createOverheadAirdromeRoutePointData(aerodrome)
end
function dcsCommon.createLandAtAerodromeRoutePointData(aerodrome) function dcsCommon.createLandAtAerodromeRoutePointData(aerodrome)
@ -1418,7 +1475,7 @@ dcsCommon.version = "2.9.0"
rp.airdromeId = aerodrome:getID() rp.airdromeId = aerodrome:getID()
rp.x = p.x rp.x = p.x
rp.y = p.z rp.y = p.z
rp.alt = p.y rp.alt = land.getHeight({x=p.x, y=p.z}) --p.y
rp.action = "Landing" rp.action = "Landing"
rp.type = "Land" rp.type = "Land"
@ -1427,6 +1484,19 @@ dcsCommon.version = "2.9.0"
return rp return rp
end end
function dcsCommon.createSimpleRoutePointData(p, alt)
if not alt then alt = 8000 end -- 24'000 feet
local rp = {}
rp.x = p.x
rp.y = p.z
rp.alt = alt
rp.action = "Turning Point"
rp.type = "Turning Point"
rp.speed = 133; -- in m/s? If so, that's 360 km/h
rp.alt_type = "BARO"
return rp
end
function dcsCommon.createRPFormationData(findex) -- must be added as "task" to an RP. use 4 for Echelon right function dcsCommon.createRPFormationData(findex) -- must be added as "task" to an RP. use 4 for Echelon right
local task = {} local task = {}
@ -2481,8 +2551,8 @@ end
if not inrecursion then if not inrecursion then
-- output a marker to find in the log / screen -- output a marker to find in the log / screen
trigger.action.outText("=== dcsCommon vardump END", 30) trigger.action.outText("=== dcsCommon vardump end", 30)
env.info("=== dcsCommon vardump END") env.info("=== dcsCommon vardump end")
end end
end end
@ -2520,8 +2590,8 @@ end
if not inrecursion then if not inrecursion then
-- output a marker to find in the log / screen -- output a marker to find in the log / screen
trigger.action.outText("=== dcsCommon vardump END", 30) trigger.action.outText("=== dcsCommon vardump end", 30)
--env.info("=== dcsCommon vardump END") --env.info("=== dcsCommon vardump end")
end end
end end
@ -2541,15 +2611,19 @@ end
if id == 0 then return "invalid" end if id == 0 then return "invalid" end
-- translate the event id to text -- translate the event id to text
local events = {"shot", "hit", "takeoff", "land", local events = {"shot", "hit", "takeoff", "land",
"crash", "eject", "refuel", "dead", "crash", "eject", "refuel", "dead", -- 8
"pilot dead", "base captured", "mission start", "mission end", -- 12 "pilot dead", "base captured", "mission start", "mission end", -- 12
"took control", "refuel stop", "birth", "human failure", "took control", "refuel stop", "birth", "human failure", -- 16
"det. failure", "engine start", "engine stop", "player enter unit", "det. failure", "engine start", "engine stop", "player enter unit", -- 20
"player leave unit", "player comment", "start shoot", "end shoot", "player leave unit", "player comment", "start shoot", "end shoot", -- 24
"mark add", "mark changed", "makr removed", "kill", "mark add", "mark changed", "mark removed", "kill", -- 28
"score", "unit lost", "land after eject", "Paratrooper land", "score", "unit lost", "land after eject", "Paratrooper land", -- 32
"chair discard after eject", "weapon add", "trigger zone", "landing quality mark", "chair discard after eject", "weapon add", "trigger zone", "landing quality mark", -- 36
"BDA", "max"} "BDA", "AI Abort Mission", "DayNight", "Flight Time", -- 40
"Pilot Suicide", "player cap airfield", "emergency landing", "unit create task", -- 44
"unit delete task", "Simulation start", "weapon rearm", "weapon drop", -- 48
"unit task timeout", "unit task stage",
"max"}
if id > #events then return "Unknown (ID=" .. id .. ")" end if id > #events then return "Unknown (ID=" .. id .. ")" end
return events[id] return events[id]
end end

253
modules/duel.lua Normal file
View File

@ -0,0 +1,253 @@
duel = {}
duel.version = "1.0.0"
duel.verbose = false
duel.requiredLibs = {
"dcsCommon",
"cfxZones",
"cfxMX",
}
--[[--
Version History
1.0.0 - Initial Version
--]]--
--[[--
ATTENTION!
- REQUIRES that SSB is running on the host
- REQUIRTES that SSB is confgured that '0' (zero) means slot is enabled (this is SSB default)
- This script must run at MISSION START and will enable SSB
--]]--
duel.duelZones = {}
duel.activeDuelists = {}
duel.allDuelists = {}
--
-- reading attributes
--
function duel.createDuelZone(theZone)
theZone.duelists = {} -- all player units in this zone
-- iterate all players and find any unit that is placed in this zone
for unitName, unitData in pairs(cfxMX.playerUnitByName) do
local p = {}
p.x = unitData.x
p.z = unitData.y -- !!
p.y = 0
if theZone:pointInZone(p) then
-- this is a player aircraft in this zone
local duelist = {}
duelist.data = unitData
duelist.name = unitName
duelist.type = unitData.type
local groupData = cfxMX.playerUnit2Group[unitName]
duelist.groupName = groupData.name
duelist.coa = cfxMX.groupCoalitionByName[duelist.groupName]
if duel.verbose then
trigger.action.outText("Detected player unit <" .. duelist.name .. ">, type <" .. duelist.type .. "> of group <" .. duelist.groupName .. "> of coa <" .. duelist.coa .. "> in zone <" .. theZone.name .. "> as duelist", 30)
end
duelist.active = false
duelist.arena = theZone.name
duelist.zone = theZone
-- enter into global table
-- player can only be in at maximum one duelist zones
if duel.allDuelists[unitName] then
trigger.action.outText("+++WARNING: overlapping duelists! Overwriting previous data", 30)
end
duel.allDuelists[unitName] = duelist
theZone.duelists[unitName] = duelist
end
end
theZone.state = "waiting" -- FSM, init to waiting state
end
--
-- Event processing
--
function duel.closeSlotsForZoneAndCoaExceptGroupNamed(theZone, coa, groupName)
-- iterate this zone's duelist groups and tell SSB to close them now
local allDuelists = theZone.duelists
for unitName, theDuelist in pairs(allDuelists) do
local dgName = theDuelist.groupName
if (theDuelist.coa == coa) and (dgName ~= groupName) then
if duel.verbose then
trigger.action.outText("+++duel: closing SSB slot for group <" .. dgName .. ">, coa <" .. theDuelist.coa .. ">", 30)
trigger.action.setUserFlag(dgName,100) -- anything but 0 means closed
end
end
end
end
function duel.openSlotsForZoneAndCoa(theZone, coa)
local allDuelists = theZone.duelists
for unitName, theDuelist in pairs(allDuelists) do
if (theDuelist.coa == coa) then
if duel.verbose then
trigger.action.outText("+++duel: opening SSB slot for group <" .. theDuelist.groupName .. ">, coa <" .. theDuelist.coa .. ">", 30)
trigger.action.setUserFlag(theDuelist.groupName, 0) -- 0 means OPEN
end
end
end
end
function duel.checkReopenSlotsForZoneAndCoa(theZone, coa)
-- test if one side can reopen all slots to enter the duel
-- if so, will reset FSM for zone
local allDuelists = theZone.duelists
local allUnengaged = true
for unitName, theDuelist in pairs(allDuelists) do
if (theDuelist.coa == coa) then
local theUnit = Unit.getByName(unitName)
if theUnit and Unit.isExist(theUnit) then
-- unit is still alive on this side, can't reopen
allUnengaged = false
end
end
end
if allUnengaged then
duel.openSlotsForZoneAndCoa(theZone, coa)
theZone.state = "waiting"
end
end
function duel.duelistEnteredArena(theUnit, theDuelist)
-- we connect the player with duelist slot
theDuelist.playerName = theUnit:getPlayerName()
theDuelist.active = true
local player = theUnit:getPlayerName()
local unitName = theUnit:getName()
local groupName = theDuelist.groupName
local theZone = theDuelist.zone --duel.duelZones[theDuelist.arena]
local coa = theDuelist.coa
if duel.verbose then
trigger.action.outText("Player <" .. player .. "> entered arena <" .. theZone:getName() .. "> in unit <" .. unitName .. "> of group <" .. groupName .. "> type <" .. theDuelist.type .. ">, belongs to coalition <" .. coa .. ">", 30)
end
-- close all slots for this zone and coalition
duel.closeSlotsForZoneAndCoaExceptGroupNamed(theZone, coa, groupName)
end
function duel:onEvent(event)
if not event then return end
if duel.verbose then
--trigger.action.outText("Event: " .. event.id .. " (" .. dcsCommon.event2text(event.id) .. ")", 30)
end
local theUnit = event.initiator
if not theUnit then return end
if event.id == 15 then -- birth
local unitName = theUnit:getName()
-- see if this is a duelist that has spawned
if not duel.allDuelists[unitName] then
return -- not a duelist, not my problem
end
-- unit that entered is player controlled, and duelist
duel.duelistEnteredArena(theUnit, duel.allDuelists[unitName])
end
end
--
-- update
--
function duel.update()
-- call me in a second to poll triggers
timer.scheduleFunction(duel.update, {}, timer.getTime() + 1/duel.ups)
-- find units that have disappeared, and react accordingly
for unitName, theDuelist in pairs (duel.allDuelists) do
local theZone = theDuelist.zone
if theDuelist.active then
-- trigger.action.outText("+++duel: unit <" .. unitName .. "> is active in zone <" .. theZone:getName() .. ">, controlled by <" .. theDuelist.playerName .. ">", 30)
local theUnit = Unit.getByName(unitName)
if theUnit and Unit.isExist(theUnit) then
-- all is well
else
if duel.verbose then
trigger.action.outText("+++duel: unit <" .. unitName .. "> controlled by <" .. theDuelist.playerName .. "> has disappeared, starting cleanup", 30)
end
theDuelist.playerName = nil
theDuelist.active = false
duel.checkReopenSlotsForZoneAndCoa(theZone, theDuelist.coa)
end
end
end
-- now handle FSM for each zone separately
for zoneName, theZone in pairs(duel.duelZones) do
end
end
--
-- Config & start
--
function duel.readConfigZone()
local theZone = cfxZones.getZoneByName("duelConfig")
if not theZone then
theZone = cfxZones.createSimpleZone("duelConfig")
end
duel.verbose = theZone.verbose
duel.ups = theZone:getNumberFromZoneProperty("ups", 1)
duel.inside = theZone:getBoolFromZoneProperty("inside", true)
duel.gracePeriod = theZone:getNumberFromZoneProperty("gracePeriod", 30)
duel.keepScore = theZone:getBoolFromZoneProperty("score", true)
if duel.verbose then
trigger.action.outText("+++duel: read config", 30)
end
end
function duel.start()
if not dcsCommon.libCheck then
trigger.action.outText("cfx duel requires dcsCommon", 30)
return false
end
if not dcsCommon.libCheck("cfx Duel", duel.requiredLibs) then
return false
end
-- turn on SSB
trigger.action.setUserFlag("SSB",100)
-- read config
duel.readConfigZone()
-- process cloner Zones
local attrZones = cfxZones.getZonesWithAttributeNamed("duel")
for k, aZone in pairs(attrZones) do
duel.createDuelZone(aZone) -- process attributes
duel.duelZones[aZone.name] = aZone -- add to list
end
-- connect event handler
world.addEventHandler(duel)
-- start update
duel.update()
trigger.action.outText("cfx Duel v" .. duel.version .. " started.", 30)
return true
end
if not duel.start() then
trigger.action.outText("cfx Duel aborted: missing libraries", 30)
duel = nil
end

View File

@ -1,5 +1,5 @@
guardianAngel = {} guardianAngel = {}
guardianAngel.version = "3.0.4" guardianAngel.version = "3.0.5"
guardianAngel.ups = 10 guardianAngel.ups = 10
guardianAngel.name = "Guardian Angel" -- just in case someone accesses .name guardianAngel.name = "Guardian Angel" -- just in case someone accesses .name
guardianAngel.launchWarning = true -- detect launches and warn pilot guardianAngel.launchWarning = true -- detect launches and warn pilot
@ -61,6 +61,10 @@ guardianAngel.requiredLibs = {
3.0.3 - monitorItem() guards against loss of target (nil) 3.0.3 - monitorItem() guards against loss of target (nil)
3.0.4 - launchSound attribute 3.0.4 - launchSound attribute
- interventionSound attribute - interventionSound attribute
3.0.5 - better missiole names, their object IDs seem to have disappeared, also storing launcher name
- msgTime to control how long warnings remain on the screen
- disappear message now only on verbose
- dmlZones
This script detects missiles launched against protected aircraft an This script detects missiles launched against protected aircraft an
@ -133,7 +137,7 @@ end
-- --
-- watch q items -- watch q items
-- --
function guardianAngel.createQItem(theWeapon, theTarget, threat) function guardianAngel.createQItem(theWeapon, theTarget, threat, launcher)
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
@ -142,8 +146,26 @@ function guardianAngel.createQItem(theWeapon, theTarget, threat)
-- watch it for re-targeting purposes -- watch it for re-targeting purposes
local theItem = {} local theItem = {}
local oName = tostring(theWeapon:getName())
if not oName or #oName < 1 then oName = dcsCommon.numberUUID() end
local wName = ""
if theWeapon.getDisplayName then
wName = theWeapon:getDisplayName() -- does this even exist any more?
elseif theWeapon.getTypeName then
wName = theWeapon:getTypeName()
else
wName = "<Generic>"
end
wName = wName .. "-" .. oName
local launcherName = launcher:getTypeName() .. " " .. launcher:getName()
theItem.theWeapon = theWeapon -- weapon that we are tracking theItem.theWeapon = theWeapon -- weapon that we are tracking
theItem.weaponName = theWeapon:getName() theItem.weaponName = wName -- theWeapon:getName()
-- usually weapons have no 'name' except an ID, so let's get the
-- type or display name. Weapons often have no display name.
if guardianAngel.verbose then
trigger.action.outText("gA: tracking missile <" .. wName .. "> launched by <" .. launcherName .. ">", guardianAngel.msgTime)
end
theItem.theTarget = theTarget theItem.theTarget = theTarget
theItem.tGroup = theTarget:getGroup() theItem.tGroup = theTarget:getGroup()
theItem.tID = theItem.tGroup:getID() theItem.tID = theItem.tGroup:getID()
@ -156,6 +178,7 @@ function guardianAngel.createQItem(theWeapon, theTarget, threat)
theItem.threat = threat theItem.threat = threat
theItem.lastDesc = "(new)" theItem.lastDesc = "(new)"
theItem.timeStamp = timer.getTime() theItem.timeStamp = timer.getTime()
theItem.launcher = launcherName
return theItem return theItem
end end
@ -222,14 +245,17 @@ function guardianAngel.monitorItem(theItem)
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"
--[[--
if guardianAngel.announcer and theItem.threat then if guardianAngel.announcer and theItem.threat then
local desc = theItem.weaponName .. ": DISAPPEARED"
if guardianAngel.private then if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, 30) trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
else else
trigger.action.outText(desc, 30) trigger.action.outText(desc, guardianAngel.msgTime)
end end
end end
--]]--
if guardianAngel.verbose then if guardianAngel.verbose then
trigger.action.outText("+++gA: missile disappeared: <" .. theItem.weaponName .. ">, aimed at <" .. theItem.targetName .. ">",30) trigger.action.outText("+++gA: missile disappeared: <" .. theItem.weaponName .. ">, aimed at <" .. theItem.targetName .. ">",30)
end end
@ -277,13 +303,13 @@ function guardianAngel.monitorItem(theItem)
if isThreat and guardianAngel.announcer and guardianAngel.active then if isThreat and guardianAngel.announcer and guardianAngel.active then
local desc = "Missile, missile, missile - now heading for " .. ctName .. "!" local desc = "Missile, missile, missile - now heading for " .. ctName .. "!"
if guardianAngel.private then if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, 30) trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
if guardianAngel.launchSound then if guardianAngel.launchSound then
local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound
trigger.action.outSoundForGroup(ID, fileName) trigger.action.outSoundForGroup(ID, fileName)
end end
else else
trigger.action.outText(desc, 30) trigger.action.outText(desc, guardianAngel.msgTime)
if guardianAngel.launchSound then if guardianAngel.launchSound then
local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound
trigger.action.outSound(fileName) trigger.action.outSound(fileName)
@ -340,13 +366,13 @@ function guardianAngel.monitorItem(theItem)
if guardianAngel.announcer then if guardianAngel.announcer then
if guardianAngel.private then if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, 30) trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
if guardianAngel.interventionSound then if guardianAngel.interventionSound then
local fileName = "l10n/DEFAULT/" .. guardianAngel.interventionSound local fileName = "l10n/DEFAULT/" .. guardianAngel.interventionSound
trigger.action.outSoundForGroup(ID, fileName) trigger.action.outSoundForGroup(ID, fileName)
end end
else else
trigger.action.outText(desc, 30) trigger.action.outText(desc, guardianAngel.msgTime)
if guardianAngel.interventionSound then if guardianAngel.interventionSound then
local fileName = "l10n/DEFAULT/" .. guardianAngel.interventionSound local fileName = "l10n/DEFAULT/" .. guardianAngel.interventionSound
trigger.action.outSound(fileName) trigger.action.outSound(fileName)
@ -375,9 +401,9 @@ function guardianAngel.monitorItem(theItem)
if guardianAngel.announcer then if guardianAngel.announcer then
if guardianAngel.private then if guardianAngel.private then
trigger.action.outTextForGroup(ID, desc, 30) trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
else else
trigger.action.outText(desc, 30) trigger.action.outText(desc, guardianAngel.msgTime)
end end
end end
guardianAngel.invokeCallbacks("intervention", theItem.targetName, theItem.weaponName) guardianAngel.invokeCallbacks("intervention", theItem.targetName, theItem.weaponName)
@ -591,6 +617,7 @@ 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)
local launcher = theUnit
guardianAngel.missilesAndTargets[theWeapon:getName()] = targetName guardianAngel.missilesAndTargets[theWeapon:getName()] = targetName
if not watchedUnit then if not watchedUnit then
-- we may still want to watch this if the missile -- we may still want to watch this if the missile
@ -599,14 +626,14 @@ function guardianAngel.somethingHappened(event)
trigger.action.outText("+++gA: missile <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">, not a threat", 30) trigger.action.outText("+++gA: missile <" .. theWeapon:getName() .. "> targeting <" .. targetName .. ">, not a threat", 30)
end end
-- add it as no threat -- add it as no threat
local theQItem = guardianAngel.createQItem(theWeapon, theTarget, false) -- this is not a threat, simply watch for re-target local theQItem = guardianAngel.createQItem(theWeapon, theTarget, false, launcher) -- this is not a threat, simply watch for re-target
table.insert(guardianAngel.missilesInTheAir, theQItem) table.insert(guardianAngel.missilesInTheAir, theQItem)
return return
end -- fired at some other poor sucker, we don't care 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, true) -- this is watched local theQItem = guardianAngel.createQItem(theWeapon, theTarget, true, launcher) -- 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)
@ -624,13 +651,13 @@ function guardianAngel.somethingHappened(event)
-- 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" .. vbInfo, 30) trigger.action.outTextForGroup(grpID, "Missile, missile, missile, " .. oclock .. " o clock" .. vbInfo, guardianAngel.msgTime)
if guardianAngel.launchSound then if guardianAngel.launchSound then
local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound
trigger.action.outSoundForGroup(grpID, fileName) trigger.action.outSoundForGroup(grpID, fileName)
end end
else else
trigger.action.outText("Missile, missile, missile, " .. oclock .. " o clock" .. vbInfo, 30) trigger.action.outText("Missile, missile, missile, " .. oclock .. " o clock" .. vbInfo, guardianAngel.msgTime)
if guardianAngel.launchSound then if guardianAngel.launchSound then
local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound
trigger.action.outSound(fileName) trigger.action.outSound(fileName)
@ -847,40 +874,42 @@ function guardianAngel.readConfigZone()
end end
guardianAngel.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) guardianAngel.verbose = theZone:getBoolFromZoneProperty("verbose", false)
guardianAngel.autoAddPlayer = cfxZones.getBoolFromZoneProperty(theZone, "autoAddPlayer", true) guardianAngel.autoAddPlayer = theZone:getBoolFromZoneProperty("autoAddPlayer", true)
guardianAngel.launchWarning = cfxZones.getBoolFromZoneProperty(theZone, "launchWarning", true) guardianAngel.launchWarning = theZone:getBoolFromZoneProperty("launchWarning", true)
guardianAngel.intervention = cfxZones.getBoolFromZoneProperty(theZone, "intervention", true) guardianAngel.intervention = theZone:getBoolFromZoneProperty("intervention", true)
guardianAngel.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true) guardianAngel.announcer = theZone:getBoolFromZoneProperty( "announcer", true)
guardianAngel.private = cfxZones.getBoolFromZoneProperty(theZone, "private", false) guardianAngel.private = theZone:getBoolFromZoneProperty("private", false)
guardianAngel.explosion = cfxZones.getNumberFromZoneProperty(theZone, "explosion", -1) guardianAngel.explosion = theZone:getNumberFromZoneProperty("explosion", -1)
guardianAngel.fxDistance = cfxZones.getNumberFromZoneProperty(theZone, "fxDistance", 500) guardianAngel.fxDistance = theZone:getNumberFromZoneProperty( "fxDistance", 500)
guardianAngel.active = cfxZones.getBoolFromZoneProperty(theZone, "active", true) guardianAngel.active = theZone:getBoolFromZoneProperty("active", true)
if cfxZones.hasProperty(theZone, "activate?") then guardianAngel.msgTime = theZone:getNumberFromZoneProperty("msgTime", 30)
guardianAngel.activate = cfxZones.getStringFromZoneProperty(theZone, "activate?", "*<none>")
guardianAngel.lastActivate = cfxZones.getFlagValue(guardianAngel.activate, theZone) if theZone:hasProperty("activate?") then
elseif cfxZones.hasProperty(theZone, "on?") then guardianAngel.activate = theZone:getStringFromZoneProperty("activate?", "*<none>")
guardianAngel.activate = cfxZones.getStringFromZoneProperty(theZone, "on?", "*<none>") guardianAngel.lastActivate = theZone:getFlagValue(guardianAngel.activate)
guardianAngel.lastActivate = cfxZones.getFlagValue(guardianAngel.activate, theZone) elseif theZone:hasProperty("on?") then
guardianAngel.activate = theZone:getStringFromZoneProperty("on?", "*<none>")
guardianAngel.lastActivate = theZone:getFlagValue(guardianAngel.activate)
end end
if cfxZones.hasProperty(theZone, "deactivate?") then if theZone:hasProperty("deactivate?") then
guardianAngel.deactivate = cfxZones.getStringFromZoneProperty(theZone, "deactivate?", "*<none>") guardianAngel.deactivate = theZone:getStringFromZoneProperty("deactivate?", "*<none>")
guardianAngel.lastDeActivate = cfxZones.getFlagValue(guardianAngel.deactivate, theZone) guardianAngel.lastDeActivate = theZone:getFlagValue(guardianAngel.deactivate)
elseif cfxZones.hasProperty(theZone, "off?") then elseif theZone:hasProperty("off?") then
guardianAngel.deactivate = cfxZones.getStringFromZoneProperty(theZone, "off?", "*<none>") guardianAngel.deactivate = theZone:getStringFromZoneProperty("off?", "*<none>")
guardianAngel.lastDeActivate = cfxZones.getFlagValue(guardianAngel.deactivate, theZone) guardianAngel.lastDeActivate = theZone:getFlagValue(guardianAngel.deactivate)
end end
if cfxZones.hasProperty(theZone, "launchSound") then if theZone:hasProperty("launchSound") then
guardianAngel.launchSound = cfxZones.getStringFromZoneProperty(theZone, "launchSound", "nosound") guardianAngel.launchSound = theZone:getStringFromZoneProperty("launchSound", "nosound")
end end
if cfxZones.hasProperty(theZone, "interventionSound") then if theZone:hasProperty("interventionSound") then
guardianAngel.interventionSound = cfxZones.getStringFromZoneProperty(theZone, "interventionSound", "nosound") guardianAngel.interventionSound = theZone:getStringFromZoneProperty("interventionSound", "nosound")
end end
guardianAngel.configZone = theZone guardianAngel.configZone = theZone
@ -894,7 +923,7 @@ end
-- --
function guardianAngel.processGuardianZone(theZone) function guardianAngel.processGuardianZone(theZone)
theZone.angelic = cfxZones.getBoolFromZoneProperty(theZone, "guardian", true) theZone.angelic = theZone:getBoolFromZoneProperty("guardian", true)
if theZone.verbose or guardianAngel.verbose then if theZone.verbose or guardianAngel.verbose then
@ -947,7 +976,7 @@ function guardianAngel.start()
end end
function guardianAngel.testCB(reason, targetName, weaponName) function guardianAngel.testCB(reason, targetName, weaponName)
trigger.action.outText("gA - CB for ".. reason .. ": " .. targetName .. " w: " .. weaponName, 30) trigger.action.outText("gA - CB for ".. reason .. ": " .. targetName .. " w: " .. weaponName, guardianAngel.msgTime)
end end
-- go go go -- go go go

View File

@ -1,5 +1,5 @@
unitZone={} unitZone={}
unitZone.version = "1.2.4" unitZone.version = "1.2.5"
unitZone.verbose = false unitZone.verbose = false
unitZone.ups = 1 unitZone.ups = 1
unitZone.requiredLibs = { unitZone.requiredLibs = {
@ -17,6 +17,7 @@ unitZone.requiredLibs = {
1.2.3 - better guards for enterZone!, exitZone!, changeZone! 1.2.3 - better guards for enterZone!, exitZone!, changeZone!
- better guards for uzOn? and uzOff? - better guards for uzOn? and uzOff?
1.2.4 - more verbosity on uzDirect 1.2.4 - more verbosity on uzDirect
1.2.5 - reading config improvement
--]]-- --]]--
@ -196,8 +197,9 @@ function unitZone.checkZoneStatus(theZone)
local playerCheck = theZone.matching == "player" local playerCheck = theZone.matching == "player"
if playerCheck then if playerCheck then
-- we check the names for players only -- we check the names for players only
-- collector holds units, not groups
for idx, pUnit in pairs(theGroups) do for idx, pUnit in pairs(theGroups) do
local puName=pUnit:getName() local puName = pUnit:getName()
local hasMatch = false local hasMatch = false
if theZone.lookForBeginsWith then if theZone.lookForBeginsWith then
hasMatch = dcsCommon.stringStartsWith(puName, lookFor) hasMatch = dcsCommon.stringStartsWith(puName, lookFor)
@ -323,10 +325,7 @@ end
function unitZone.readConfigZone() function unitZone.readConfigZone()
local theZone = cfxZones.getZoneByName("unitZoneConfig") local theZone = cfxZones.getZoneByName("unitZoneConfig")
if not theZone then if not theZone then
if unitZone.verbose then theZone = cfxZones.createSimpleZone("unitZoneConfig")
trigger.action.outText("+++uZne: NO config zone!", 30)
end
return
end end
unitZone.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false) unitZone.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)