mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 1.4.1
Civ Air off-map locations Guardian Angel updates Recon Mode updates
This commit is contained in:
parent
3117bfd80c
commit
7c6deec7a6
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
cfxReconMode = {}
|
||||
cfxReconMode.version = "2.1.4"
|
||||
cfxReconMode.version = "2.2.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
|
||||
|
||||
@ -86,6 +86,10 @@ VERSION HISTORY
|
||||
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
|
||||
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
|
||||
missions and, after detecting units, marks them on the map with
|
||||
@ -355,7 +359,7 @@ function cfxReconMode.placeMarkForUnit(location, theSide, theGroup)
|
||||
theDesc,
|
||||
location,
|
||||
theSide,
|
||||
false,
|
||||
cfxReconMode.marksLocked, -- readOnly -- false,
|
||||
nil)
|
||||
return theID
|
||||
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
|
||||
-- and <mgrs> with mgrs coords of zone point
|
||||
local currPoint = cfxZones.getPoint(theZone)
|
||||
local currPoint = theZone:getPoint()
|
||||
if theGroup and theGroup:isExist() then
|
||||
-- only use group's point when group exists and alive
|
||||
local theUnit = dcsCommon.getFirstLivingUnit(theGroup)
|
||||
@ -529,7 +533,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
|
||||
end
|
||||
|
||||
-- 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 gName = theGroup:getName()
|
||||
local args = {mySide, theScout, theGroup, theID, gName}
|
||||
@ -541,10 +545,9 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
|
||||
end
|
||||
|
||||
-- say something
|
||||
if not silent and cfxReconMode.announcer then
|
||||
if (not silent) and cfxReconMode.announcer then
|
||||
local msg = cfxReconMode.generateSALT(theScout, theGroup)
|
||||
trigger.action.outTextForCoalition(mySide, msg, cfxReconMode.reportTime)
|
||||
-- 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
|
||||
@ -554,7 +557,6 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
|
||||
end
|
||||
|
||||
-- see if it was a prio target
|
||||
--local inList, gName = cfxReconMode.isStringInList(theGroup:getName(), cfxReconMode.prioList)
|
||||
if inList then
|
||||
-- if cfxReconMode.announcer then
|
||||
if cfxReconMode.verbose then
|
||||
@ -565,7 +567,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
|
||||
|
||||
-- increase prio flag
|
||||
if cfxReconMode.prioFlag then
|
||||
cfxZones.pollFlag(cfxReconMode.prioFlag, cfxReconMode.method, cfxReconMode.theZone)
|
||||
cfxReconMode.theZone:pollFlag(cfxReconMode.prioFlag, cfxReconMode.method )
|
||||
end
|
||||
|
||||
-- see if we were passed additional info in zInfo
|
||||
@ -583,7 +585,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
|
||||
end
|
||||
|
||||
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
|
||||
trigger.action.outText("+++rcn: banging <" .. zInfo.theFlag .. "> for prio target zone <" .. zInfo.theZone.name .. ">",30)
|
||||
end
|
||||
@ -595,7 +597,7 @@ function cfxReconMode.detectedGroup(mySide, theScout, theGroup, theLoc)
|
||||
|
||||
-- increase normal flag
|
||||
if cfxReconMode.detectFlag then
|
||||
cfxZones.pollFlag(cfxReconMode.detectFlag, cfxReconMode.method, cfxReconMode.theZone)
|
||||
cfxReconMode.theZone:pollFlag(cfxReconMode.detectFlag, cfxReconMode.method)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -655,7 +657,7 @@ function cfxReconMode.doDeActivate()
|
||||
end
|
||||
end
|
||||
|
||||
function cfxReconMode.updateQueues()
|
||||
function cfxReconSMode.updateQueues()
|
||||
-- schedule next call
|
||||
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
|
||||
-- scouts table until it's empty. When empty, transfer all
|
||||
-- back and start cycle anew
|
||||
|
||||
local theFocusScoutName = nil
|
||||
local procCount = 0 -- no iterations done yet
|
||||
for name, scout in pairs(cfxReconMode.scouts) do
|
||||
@ -736,7 +737,6 @@ function cfxReconMode.autoRemove()
|
||||
local toRemove = {}
|
||||
-- scan all marked groups, and when they no longer exist, remove them
|
||||
for idx, args in pairs (cfxReconMode.activeMarks) do
|
||||
-- args = {mySide, theScout, theGroup, theID, gName}
|
||||
local gName = args[5]
|
||||
if not cfxReconMode.isGroupStillAlive(gName) then
|
||||
-- remove mark, remove group from set
|
||||
@ -766,7 +766,7 @@ function cfxReconMode.lateEvalPlayerUnit(theUnit)
|
||||
for idx, theZone in pairs (cfxReconMode.scoutZones) do
|
||||
local isScout = theZone.isScout
|
||||
local dynamic = theZone.dynamic
|
||||
local inZone = cfxZones.pointInZone(p, theZone)
|
||||
local inZone = theZone:pointInZone(p)
|
||||
if inZone then
|
||||
if isScout then
|
||||
cfxReconMode.addToAllowedScoutList(aGroup, dynamic)
|
||||
@ -886,7 +886,6 @@ function cfxReconMode:onEvent(event)
|
||||
end
|
||||
cfxReconMode.addScout(theUnit)
|
||||
end
|
||||
-- trigger.action.outText("+++rcn-onEvent: " .. event.id .. " for <" .. theUnit:getName() .. ">", 30)
|
||||
end
|
||||
|
||||
--
|
||||
@ -984,68 +983,70 @@ function cfxReconMode.readConfigZone()
|
||||
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.redScouts = cfxZones.getBoolFromZoneProperty(theZone, "redScouts", false)
|
||||
cfxReconMode.blueScouts = cfxZones.getBoolFromZoneProperty(theZone, "blueScouts", true)
|
||||
cfxReconMode.greyScouts = cfxZones.getBoolFromZoneProperty(theZone, "greyScouts", false)
|
||||
cfxReconMode.playerOnlyRecon = cfxZones.getBoolFromZoneProperty(theZone, "playerOnlyRecon", false)
|
||||
cfxReconMode.reportNumbers = cfxZones.getBoolFromZoneProperty(theZone, "reportNumbers", true)
|
||||
cfxReconMode.reportTime = cfxZones.getNumberFromZoneProperty(theZone, "reportTime", 30)
|
||||
cfxReconMode.autoRecon = theZone:getBoolFromZoneProperty("autoRecon", true)
|
||||
cfxReconMode.redScouts = theZone:getBoolFromZoneProperty("redScouts", false)
|
||||
cfxReconMode.blueScouts = theZone:getBoolFromZoneProperty( "blueScouts", true)
|
||||
cfxReconMode.greyScouts = theZone:getBoolFromZoneProperty( "greyScouts", false)
|
||||
cfxReconMode.playerOnlyRecon = theZone:getBoolFromZoneProperty("playerOnlyRecon", false)
|
||||
cfxReconMode.reportNumbers = theZone:getBoolFromZoneProperty( "reportNumbers", true)
|
||||
cfxReconMode.reportTime = theZone:getNumberFromZoneProperty( "reportTime", 30)
|
||||
|
||||
cfxReconMode.detectionMinRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionMinRange", 3000)
|
||||
cfxReconMode.detectionMaxRange = cfxZones.getNumberFromZoneProperty(theZone, "detectionMaxRange", 12000)
|
||||
cfxReconMode.maxAlt = cfxZones.getNumberFromZoneProperty(theZone, "maxAlt", 9000)
|
||||
cfxReconMode.detectionMinRange = theZone:getNumberFromZoneProperty("detectionMinRange", 3000)
|
||||
cfxReconMode.detectionMaxRange = theZone:getNumberFromZoneProperty("detectionMaxRange", 12000)
|
||||
cfxReconMode.maxAlt = theZone:getNumberFromZoneProperty("maxAlt", 9000)
|
||||
|
||||
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>")
|
||||
if theZone:hasProperty("prio+") then -- deprecated. remove next update
|
||||
cfxReconMode.prioFlag = theZone:getStringFromZoneProperty("prio+", "none")
|
||||
elseif theZone:hasProperty("prio!") then
|
||||
cfxReconMode.prioFlag = theZone:getStringFromZoneProperty("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>")
|
||||
if theZone:hasProperty("detect+") then -- deprecated
|
||||
cfxReconMode.detectFlag = theZone:getStringFromZoneProperty("detect+", "none")
|
||||
elseif theZone:hasProperty("detect!") then
|
||||
cfxReconMode.detectFlag = theZone:getStringFromZoneProperty("detect!", "*<none>")
|
||||
end
|
||||
|
||||
cfxReconMode.method = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
|
||||
if cfxZones.hasProperty(theZone, "reconMethod") then
|
||||
cfxReconMode.method = cfxZones.getStringFromZoneProperty(theZone, "reconMethod", "inc")
|
||||
cfxReconMode.method = theZone:getStringFromZoneProperty("method", "inc")
|
||||
if theZone:hasProperty("reconMethod") then
|
||||
cfxReconMode.method = theZone:getStringFromZoneProperty("reconMethod", "inc")
|
||||
end
|
||||
|
||||
cfxReconMode.applyMarks = cfxZones.getBoolFromZoneProperty(theZone, "applyMarks", true)
|
||||
cfxReconMode.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
|
||||
-- trigger.action.outText("recon: announcer is " .. dcsCommon.bool2Text(cfxReconMode.announcer), 30) -- announced
|
||||
if cfxZones.hasProperty(theZone, "reconSound") then
|
||||
cfxReconMode.reconSound = cfxZones.getStringFromZoneProperty(theZone, "reconSound", "<nosound>")
|
||||
cfxReconMode.applyMarks = theZone:getBoolFromZoneProperty( "applyMarks", true)
|
||||
cfxReconMode.marksFadeAfter = theZone:getNumberFromZoneProperty("marksFadeAfter", 30*60) -- 30 minutes default
|
||||
cfxReconMode.marksLocked = theZone:getBoolFromZoneProperty("marksLocked", false) -- if true, players cannot remove the marks
|
||||
cfxReconMode.announcer = theZone:getBoolFromZoneProperty( "announcer", true)
|
||||
|
||||
if theZone:hasProperty("reconSound") then
|
||||
cfxReconMode.reconSound = theZone:getStringFromZoneProperty("reconSound", "<nosound>")
|
||||
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)
|
||||
if cfxZones.hasProperty(theZone, "activate?") then
|
||||
cfxReconMode.activate = cfxZones.getStringFromZoneProperty(theZone, "activate?", "*<none>")
|
||||
cfxReconMode.lastActivate = cfxZones.getFlagValue(cfxReconMode.activate, theZone)
|
||||
elseif cfxZones.hasProperty(theZone, "on?") then
|
||||
cfxReconMode.activate = cfxZones.getStringFromZoneProperty(theZone, "on?", "*<none>")
|
||||
cfxReconMode.lastActivate = cfxZones.getFlagValue(cfxReconMode.activate, theZone)
|
||||
cfxReconMode.active = theZone:getBoolFromZoneProperty("active", true)
|
||||
if theZone:hasProperty("activate?") then
|
||||
cfxReconMode.activate = theZone:getStringFromZoneProperty("activate?", "*<none>")
|
||||
cfxReconMode.lastActivate = theZone:getFlagValue(cfxReconMode.activate)
|
||||
elseif theZone:hasProperty("on?") then
|
||||
cfxReconMode.activate = theZone:getStringFromZoneProperty("on?", "*<none>")
|
||||
cfxReconMode.lastActivate = theZone:getFlagValue(cfxReconMode.activate)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "deactivate?") then
|
||||
cfxReconMode.deactivate = cfxZones.getStringFromZoneProperty(theZone, "deactivate?", "*<none>")
|
||||
cfxReconMode.lastDeActivate = cfxZones.getFlagValue(cfxReconMode.deactivate, theZone)
|
||||
elseif cfxZones.hasProperty(theZone, "off?") then
|
||||
cfxReconMode.deactivate = cfxZones.getStringFromZoneProperty(theZone, "off?", "*<none>")
|
||||
cfxReconMode.lastDeActivate = cfxZones.getFlagValue(cfxReconMode.deactivate, theZone)
|
||||
if theZone:hasProperty("deactivate?") then
|
||||
cfxReconMode.deactivate = theZone:getStringFromZoneProperty("deactivate?", "*<none>")
|
||||
cfxReconMode.lastDeActivate = theZone:getFlagValue(cfxReconMode.deactivate)
|
||||
elseif theZone:hasProperty("off?") then
|
||||
cfxReconMode.deactivate = theZone:getStringFromZoneProperty("off?", "*<none>")
|
||||
cfxReconMode.lastDeActivate = theZone:getFlagValue(cfxReconMode.deactivate)
|
||||
end
|
||||
|
||||
cfxReconMode.imperialUnits = cfxZones.getBoolFromZoneProperty(theZone, "imperial", false)
|
||||
if cfxZones.hasProperty(theZone, "imperialUnits") then
|
||||
cfxReconMode.imperialUnits = cfxZones.getBoolFromZoneProperty(theZone, "imperialUnits", false)
|
||||
cfxReconMode.imperialUnits = theZone:getBoolFromZoneProperty("imperial", false)
|
||||
if theZone:hasProperty("imperialUnits") then
|
||||
cfxReconMode.imperialUnits = theZone:getBoolFromZoneProperty( "imperialUnits", false)
|
||||
end
|
||||
|
||||
cfxReconMode.theZone = theZone -- save this zone
|
||||
@ -1057,24 +1058,24 @@ end
|
||||
|
||||
|
||||
function cfxReconMode.processReconZone(theZone)
|
||||
local theList = cfxZones.getStringFromZoneProperty(theZone, "recon", "prio")
|
||||
local theList = theZone:getStringFromZoneProperty("recon", "prio")
|
||||
theList = string.upper(theList)
|
||||
local isBlack = dcsCommon.stringStartsWith(theList, "BLACK")
|
||||
|
||||
local zInfo = {}
|
||||
zInfo.theZone = theZone
|
||||
zInfo.isBlack = isBlack
|
||||
zInfo.silent = cfxZones.getBoolFromZoneProperty(theZone, "silent", false)
|
||||
zInfo.silent = theZone:getBoolFromZoneProperty("silent", false)
|
||||
|
||||
if cfxZones.hasProperty(theZone, "spotted!") then
|
||||
zInfo.theFlag = cfxZones.getStringFromZoneProperty(theZone, "spotted!", "*<none>")
|
||||
if theZone:hasProperty("spotted!") then
|
||||
zInfo.theFlag = theZone:getStringFromZoneProperty("spotted!", "*<none>")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "prioMessage") then
|
||||
zInfo.prioMessage = cfxZones.getStringFromZoneProperty(theZone, "prioMessage", "<none>")
|
||||
if theZone:hasProperty("prioMessage") then
|
||||
zInfo.prioMessage = theZone:getStringFromZoneProperty("prioMessage", "<none>")
|
||||
end
|
||||
|
||||
local dynamic = cfxZones.getBoolFromZoneProperty(theZone, "dynamic", false)
|
||||
local dynamic = theZone:getBoolFromZoneProperty("dynamic", false)
|
||||
zInfo.dynamic = dynamic
|
||||
local categ = 2 -- ground troops only
|
||||
local allGroups = cfxZones.allGroupsInZone(theZone, categ)
|
||||
@ -1100,15 +1101,15 @@ function cfxReconMode.processReconZone(theZone)
|
||||
end
|
||||
|
||||
function cfxReconMode.processScoutZone(theZone)
|
||||
local isScout = cfxZones.getBoolFromZoneProperty(theZone, "scout", true)
|
||||
local dynamic = cfxZones.getBoolFromZoneProperty(theZone, "dynamic")
|
||||
local isScout = theZone:getBoolFromZoneProperty("scout", true)
|
||||
local dynamic = theZone:getBoolFromZoneProperty("dynamic")
|
||||
theZone.dynamic = dynamic
|
||||
theZone.isScout = isScout
|
||||
|
||||
local categ = 0 -- aircraft
|
||||
local allFixed = cfxZones.allGroupsInZone(theZone, categ)
|
||||
local allFixed = theZone:allGroupsInZone(categ)
|
||||
local categ = 1 -- helos
|
||||
local allRotor = cfxZones.allGroupsInZone(theZone, categ)
|
||||
local allRotor = theZone:allGroupsInZone(categ)
|
||||
local allGroups = dcsCommon.combineTables(allFixed, allRotor)
|
||||
for idx, aGroup in pairs(allGroups) do
|
||||
if isScout then
|
||||
|
||||
@ -149,6 +149,7 @@ cfxZones.version = "4.0.0"
|
||||
- getNumberFromZoneProperty() enforces number return even on default
|
||||
- immediate method switched to preceeding '#', to resolve conflict witzh
|
||||
negative numbers, backwards compatibility with old (dysfunctional) method
|
||||
- 4.0.1 - dmlZone:getName()
|
||||
--]]--
|
||||
|
||||
--
|
||||
@ -3258,6 +3259,10 @@ function dmlZone:getPoint(getHeight)
|
||||
return thePos
|
||||
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!!!!
|
||||
theZone.linkedUnit = theUnit
|
||||
if not dx then dx = 0 end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
civAir = {}
|
||||
civAir.version = "1.5.2"
|
||||
civAir.version = "2.0.0"
|
||||
--[[--
|
||||
1.0.0 initial version
|
||||
1.1.0 exclude list for airfields
|
||||
@ -23,7 +23,15 @@ civAir.version = "1.5.2"
|
||||
exclude list and include list
|
||||
1.5.1 added depart only and arrive only options for airfields
|
||||
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
|
||||
-- but "exclude", the closest airfield to the zone
|
||||
-- is added to trafficCenters
|
||||
|
||||
-- if you leave this list empty, and do not add airfields
|
||||
-- by zones, the list is automatically populated with all
|
||||
-- airfields in the map
|
||||
-- if name starts with "***" then it is not an airfield, but zone
|
||||
|
||||
civAir.excludeAirfields = {}
|
||||
-- list all airfields that must NOT be included in
|
||||
-- civilian activities. Will be used for neither landing
|
||||
-- nor departure. overrides any airfield that was included
|
||||
-- in trafficCenters. Here, Senaki is off limits for
|
||||
-- civilian air traffic
|
||||
-- in trafficCenters.
|
||||
-- can be populated by zone on the map that have the
|
||||
-- 'civAir' attribute with value "exclude"
|
||||
|
||||
civAir.departOnly = {} -- use only to start from
|
||||
civAir.landingOnly = {} -- use only to land at
|
||||
civAir.inoutZones = {} -- off-map connector zones
|
||||
|
||||
civAir.requiredLibs = {
|
||||
"dcsCommon", -- common is of course needed for everything
|
||||
@ -72,57 +80,73 @@ civAir.requiredLibs = {
|
||||
|
||||
civAir.activePlanes = {}
|
||||
civAir.idlePlanes = {}
|
||||
civAir.outboundFlights = {} -- only flights that are enroute to an outbound zone
|
||||
|
||||
function civAir.readConfigZone()
|
||||
-- note: must match exactly!!!!
|
||||
local theZone = cfxZones.getZoneByName("civAirConfig")
|
||||
if not theZone then
|
||||
trigger.action.outText("***civA: NO config zone!", 30)
|
||||
return
|
||||
theZone = cfxZones.createSimpleZone("civAirConfig")
|
||||
end
|
||||
|
||||
trigger.action.outText("civA: found config zone!", 30)
|
||||
|
||||
|
||||
-- ok, for each property, load it if it exists
|
||||
if cfxZones.hasProperty(theZone, "aircraftTypes") then
|
||||
local theTypes = cfxZones.getStringFromZoneProperty(theZone, "aircraftTypes", "Yak-40")
|
||||
if theZone:hasProperty("aircraftTypes") then
|
||||
local theTypes = theZone:getStringFromZoneProperty( "aircraftTypes", civAir.aircraftTypes) -- "Yak-40")
|
||||
local typeArray = dcsCommon.splitString(theTypes, ",")
|
||||
typeArray = dcsCommon.trimArray(typeArray)
|
||||
civAir.aircraftTypes = typeArray
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "ups") then
|
||||
civAir.ups = cfxZones.getNumberFromZoneProperty(theZone, "ups", 0.05)
|
||||
if theZone:hasProperty("ups") then
|
||||
civAir.ups = theZone:getNumberFromZoneProperty("ups", 0.05)
|
||||
if civAir.ups < .0001 then civAir.ups = 0.05 end
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "maxTraffic") then
|
||||
civAir.maxTraffic = cfxZones.getNumberFromZoneProperty(theZone, "maxTraffic", 10)
|
||||
if theZone:hasProperty("maxTraffic") then
|
||||
civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxTraffic", 10)
|
||||
elseif theZone:hasProperty("maxFlights") then
|
||||
civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxFlights", 10)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "maxIdle") then
|
||||
civAir.maxIdle = cfxZones.getNumberFromZoneProperty(theZone, "maxIdle", 8 * 60)
|
||||
if theZone:hasProperty("maxIdle") then
|
||||
civAir.maxIdle = theZone:getNumberFromZoneProperty("maxIdle", 8 * 60)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "initialAirSpawns") then
|
||||
civAir.initialAirSpawns = cfxZones.getBoolFromZoneProperty(theZone, "initialAirSpawns", true)
|
||||
if theZone:hasProperty("initialAirSpawns") then
|
||||
civAir.initialAirSpawns = theZone:getBoolFromZoneProperty( "initialAirSpawns", true)
|
||||
end
|
||||
|
||||
civAir.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
civAir.verbose = theZone.verbose -- cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
end
|
||||
|
||||
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 inoutName = "***" .. theZone:getName()
|
||||
|
||||
if af then
|
||||
local afName = af:getName()
|
||||
value = value:lower()
|
||||
if value == "exclude" then
|
||||
if value == "exclude" or value == "closed" then
|
||||
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)
|
||||
elseif dcsCommon.stringStartsWith(value, "land") or dcsCommon.stringStartsWith(value, "arriv") then
|
||||
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
|
||||
table.insert(civAir.trafficCenters, afName) -- note that adding the same twice makes it more likely to be picked
|
||||
end
|
||||
@ -142,10 +166,11 @@ function civAir.removePlaneGroupByName(aName)
|
||||
return
|
||||
end
|
||||
if civAir.activePlanes[aName] then
|
||||
--trigger.action.outText("civA: REMOVING " .. aName .. " ***", 30)
|
||||
civAir.activePlanes[aName] = nil
|
||||
else
|
||||
trigger.action.outText("civA: warning - ".. aName .." remove req but not found", 30)
|
||||
if civAir.verbose then
|
||||
trigger.action.outText("civA: warning - ".. aName .." remove req but not found", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -207,10 +232,25 @@ function civAir.getTwoAirbases()
|
||||
tries = tries + 1 -- only try 10 times
|
||||
until fAB ~= sAB or tries > 10
|
||||
|
||||
fAB = dcsCommon.getFirstAirbaseWhoseNameContains(fAB, 0)
|
||||
sAB = dcsCommon.getFirstAirbaseWhoseNameContains(sAB, 0)
|
||||
|
||||
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 fAB, sAB
|
||||
return civA, civB -- fAB, sAB
|
||||
end
|
||||
|
||||
function civAir.parkingIsFree(fromWP)
|
||||
@ -222,9 +262,9 @@ function civAir.parkingIsFree(fromWP)
|
||||
loc.z = fromWP.z
|
||||
|
||||
for name, aPlaneGroup in pairs(civAir.activePlanes) do
|
||||
if aPlaneGroup:isExist() then
|
||||
if Group.isExist(aPlaneGroup) then
|
||||
local aPlane = aPlaneGroup:getUnit(1)
|
||||
if aPlane:isExist() then
|
||||
if aPlane and Unit.isExist(aPlane) then
|
||||
pos = aPlane:getPoint()
|
||||
local delta = dcsCommon.dist(loc, pos)
|
||||
if delta < 21 then
|
||||
@ -242,21 +282,35 @@ end
|
||||
civAir.airStartSeparation = 0
|
||||
function civAir.createFlight(name, theTypeString, fromAirfield, toAirfield, inAirStart)
|
||||
if not fromAirfield then
|
||||
trigger.action.outText("civA: NIL fromAirfield", 30)
|
||||
trigger.action.outText("civA: NIL source", 30)
|
||||
return nil
|
||||
end
|
||||
|
||||
if not toAirfield then
|
||||
trigger.action.outText("civA: NIL toAirfield", 30)
|
||||
trigger.action.outText("civA: NIL destination", 30)
|
||||
return nil
|
||||
end
|
||||
|
||||
local randomizeLoc = inAirStart
|
||||
|
||||
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
|
||||
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
|
||||
trigger.action.outText("civA: fromWP create failed", 30)
|
||||
return nil
|
||||
@ -266,38 +320,81 @@ function civAir.createFlight(name, theTypeString, fromAirfield, toAirfield, inAi
|
||||
fromWP.alt = fromWP.alt + 3000 + civAir.airStartSeparation -- 9000 ft overhead + separation
|
||||
fromWP.action = "Turning Point"
|
||||
fromWP.type = "Turning Point"
|
||||
|
||||
|
||||
fromWP.speed = 150;
|
||||
fromWP.airdromeId = nil
|
||||
|
||||
theAUnit.alt = fromWP.alt
|
||||
theAUnit.speed = fromWP.speed
|
||||
end
|
||||
-- sometimes, when landing kicks in too early, the plane lands
|
||||
-- at the wrong airfield. AI sucks.
|
||||
-- so we force overflight of target airfield
|
||||
local overheadWP = dcsCommon.createOverheadAirdromeRoutPintData(toAirfield)
|
||||
local toWP = dcsCommon.createLandAtAerodromeRoutePointData(toAirfield)
|
||||
if not toWP then
|
||||
trigger.action.outText("civA: toWP create failed", 30)
|
||||
return nil
|
||||
end
|
||||
|
||||
if not civAir.parkingIsFree(fromWP) then
|
||||
trigger.action.outText("civA: failed free parking check for flight " .. name, 30)
|
||||
return nil
|
||||
-- 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
|
||||
-- at the wrong airfield. AI sucks.
|
||||
-- so we force overflight of target airfield
|
||||
overheadWP = dcsCommon.createOverheadAirdromeRoutPintData(toAirfield.AB)
|
||||
toWP = dcsCommon.createLandAtAerodromeRoutePointData(toAirfield.AB)
|
||||
if not toWP then
|
||||
trigger.action.outText("civA: toWP create failed", 30)
|
||||
return nil
|
||||
end
|
||||
|
||||
if not civAir.parkingIsFree(fromWP) then
|
||||
trigger.action.outText("civA: failed free parking check for flight " .. name, 30)
|
||||
return nil
|
||||
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,
|
||||
fromWP.x,
|
||||
fromWP.y)
|
||||
dcsCommon.addRoutePointForGroupData(theGroup, fromWP)
|
||||
dcsCommon.addRoutePointForGroupData(theGroup, overheadWP)
|
||||
if not zoneApproach then
|
||||
dcsCommon.addRoutePointForGroupData(theGroup, overheadWP)
|
||||
end
|
||||
dcsCommon.addRoutePointForGroupData(theGroup, toWP)
|
||||
|
||||
-- spawn
|
||||
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
|
||||
end
|
||||
|
||||
@ -312,7 +409,9 @@ function civAir.createNewFlight(inAirStart)
|
||||
return
|
||||
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 theFlight = civAir.createFlight(name, TypeString, fAB, sAB, inAirStart)
|
||||
|
||||
@ -325,7 +424,7 @@ function civAir.createNewFlight(inAirStart)
|
||||
civAir.addPlane(theFlight) -- track it
|
||||
|
||||
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
|
||||
|
||||
@ -340,9 +439,40 @@ function civAir.airStartPopulation()
|
||||
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()
|
||||
-- reschedule me in the future. ups = updates per second.
|
||||
timer.scheduleFunction(civAir.update, {}, timer.getTime() + 1/civAir.ups)
|
||||
@ -359,12 +489,15 @@ function civAir.update()
|
||||
|
||||
for idx, name in pairs(removeMe) do
|
||||
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
|
||||
|
||||
|
||||
-- now, run through all existing flights and update their
|
||||
-- idle times. also count how many planes there are
|
||||
-- so we can respawn if we are below max
|
||||
local planeNum = 0
|
||||
local overduePlanes = {}
|
||||
local now = timer.getTime()
|
||||
@ -372,18 +505,17 @@ function civAir.update()
|
||||
local speed = 0
|
||||
if aPlaneGroup:isExist() then
|
||||
local aPlane = aPlaneGroup:getUnit(1)
|
||||
|
||||
if aPlane and aPlane:isExist() and aPlane:getLife() >= 1 then
|
||||
if aPlane and Unit.isExist(aPlane) and aPlane:getLife() >= 1 then
|
||||
planeNum = planeNum + 1
|
||||
local vel = aPlane:getVelocity()
|
||||
speed = dcsCommon.mag(vel.x, vel.y, vel.z)
|
||||
else
|
||||
-- force removal of group
|
||||
-- force removal of group, plane no longer exists
|
||||
civAir.idlePlanes[name] = -1000
|
||||
speed = 0
|
||||
end
|
||||
else
|
||||
-- force removal
|
||||
-- force removal, group no longer exists
|
||||
civAir.idlePlanes[name] = -1000
|
||||
speed = 0
|
||||
end
|
||||
@ -398,10 +530,9 @@ function civAir.update()
|
||||
table.insert(overduePlanes, name)
|
||||
end
|
||||
else
|
||||
-- zero out idle plane
|
||||
-- zero out idle plane, it's moving fast enough
|
||||
civAir.idlePlanes[name] = nil
|
||||
end
|
||||
--]]--
|
||||
end
|
||||
|
||||
-- see if we have less than max flights running
|
||||
@ -414,9 +545,12 @@ function civAir.update()
|
||||
for idx, aName in pairs(overduePlanes) do
|
||||
local aFlight = civAir.getPlane(aName) -- returns a group
|
||||
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!
|
||||
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
|
||||
@ -504,7 +638,9 @@ function civAir.start()
|
||||
|
||||
-- start the update loop
|
||||
civAir.update()
|
||||
|
||||
-- start outbound tracking
|
||||
civAir.trackOutbound()
|
||||
|
||||
-- say hi!
|
||||
trigger.action.outText("cf/x civAir v" .. civAir.version .. " started.", 30)
|
||||
return true
|
||||
@ -518,10 +654,11 @@ end
|
||||
--[[--
|
||||
Additional ideas
|
||||
|
||||
- border zones: ac can airstart in there and disappear in there
|
||||
- callbacks for civ spawn / despawn
|
||||
- add civkill callback / redCivKill blueCivKill flag bangers
|
||||
- Helicopter support
|
||||
- departure only, destination only
|
||||
- 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
|
||||
|
||||
--]]--
|
||||
@ -1,5 +1,5 @@
|
||||
dcsCommon = {}
|
||||
dcsCommon.version = "2.9.0"
|
||||
dcsCommon.version = "2.9.2"
|
||||
--[[-- VERSION HISTORY
|
||||
2.2.6 - compassPositionOfARelativeToB
|
||||
- clockPositionOfARelativeToB
|
||||
@ -166,7 +166,12 @@ dcsCommon.version = "2.9.0"
|
||||
2.9.0 - createPoint() moved from cfxZones
|
||||
- copyPoint() 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.troopCarriers = {"Mi-8MT", "UH-1H", "Mi-24P"} -- Ka-50, Apache and Gazelle can't carry troops
|
||||
dcsCommon.coalitionSides = {0, 1, 2}
|
||||
dcsCommon.maxCountry = 86 -- number of countries defined in total
|
||||
|
||||
-- lookup tables
|
||||
dcsCommon.groupID2Name = {}
|
||||
@ -747,23 +753,23 @@ dcsCommon.version = "2.9.0"
|
||||
return 0
|
||||
end
|
||||
if not B then
|
||||
trigger.action.outText("WARNING: no 'A' in bearingFromAtoB", 30)
|
||||
trigger.action.outText("WARNING: no 'B' in bearingFromAtoB", 30)
|
||||
return 0
|
||||
end
|
||||
if not A.x then
|
||||
trigger.action.outText("WARNING: no 'A.x' (type A =<" .. type(A) .. ">)in bearingFromAtoB", 30)
|
||||
return 0
|
||||
end
|
||||
if not A.y then
|
||||
trigger.action.outText("WARNING: no 'A.x' (type A =<" .. type(A) .. ">)in bearingFromAtoB", 30)
|
||||
if not A.z then
|
||||
trigger.action.outText("WARNING: no 'A.z' (type A =<" .. type(A) .. ">)in bearingFromAtoB", 30)
|
||||
return 0
|
||||
end
|
||||
if not B.x then
|
||||
trigger.action.outText("WARNING: no 'B.x' (type B =<" .. type(B) .. ">)in bearingFromAtoB", 30)
|
||||
return 0
|
||||
end
|
||||
if not B.y then
|
||||
trigger.action.outText("WARNING: no 'B.y' (type B =<" .. type(B) .. ">)in bearingFromAtoB", 30)
|
||||
if not B.z then
|
||||
trigger.action.outText("WARNING: no 'B.z' (type B =<" .. type(B) .. ">)in bearingFromAtoB", 30)
|
||||
return 0
|
||||
end
|
||||
|
||||
@ -773,6 +779,38 @@ dcsCommon.version = "2.9.0"
|
||||
return bearing
|
||||
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)
|
||||
local bearing = dcsCommon.bearingFromAtoB(A, B) -- in rads
|
||||
bearing = math.floor(bearing / math.pi * 180)
|
||||
@ -1116,7 +1154,7 @@ dcsCommon.version = "2.9.0"
|
||||
-- coalition's countries
|
||||
-- we start with id=0 (Russia), go to id=85 (Slovenia), but skip id = 14
|
||||
local i = 0
|
||||
while i < 86 do
|
||||
while i < dcsCommon.maxCountry do -- 86 do
|
||||
if i ~= 14 then
|
||||
if (coalition.getCountryCoalition(i) == aCoalition) then return i end
|
||||
end
|
||||
@ -1125,6 +1163,22 @@ dcsCommon.version = "2.9.0"
|
||||
|
||||
return nil
|
||||
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
|
||||
@ -1394,7 +1448,7 @@ dcsCommon.version = "2.9.0"
|
||||
return rp
|
||||
end
|
||||
|
||||
function dcsCommon.createOverheadAirdromeRoutPintData(aerodrome)
|
||||
function dcsCommon.createOverheadAirdromeRoutePointData(aerodrome)
|
||||
if not aerodrome then return nil end
|
||||
local rp = {}
|
||||
local p = aerodrome:getPoint()
|
||||
@ -1408,6 +1462,9 @@ dcsCommon.version = "2.9.0"
|
||||
rp.alt_type = "BARO"
|
||||
return rp
|
||||
end
|
||||
function dcsCommon.createOverheadAirdromeRoutPintData(aerodrome) -- backwards-compat to typo
|
||||
return dcsCommon.createOverheadAirdromeRoutePointData(aerodrome)
|
||||
end
|
||||
|
||||
|
||||
function dcsCommon.createLandAtAerodromeRoutePointData(aerodrome)
|
||||
@ -1418,7 +1475,7 @@ dcsCommon.version = "2.9.0"
|
||||
rp.airdromeId = aerodrome:getID()
|
||||
rp.x = p.x
|
||||
rp.y = p.z
|
||||
rp.alt = p.y
|
||||
rp.alt = land.getHeight({x=p.x, y=p.z}) --p.y
|
||||
rp.action = "Landing"
|
||||
rp.type = "Land"
|
||||
|
||||
@ -1427,6 +1484,19 @@ dcsCommon.version = "2.9.0"
|
||||
return rp
|
||||
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
|
||||
local task = {}
|
||||
@ -2481,8 +2551,8 @@ end
|
||||
|
||||
if not inrecursion then
|
||||
-- output a marker to find in the log / screen
|
||||
trigger.action.outText("=== dcsCommon vardump END", 30)
|
||||
env.info("=== dcsCommon vardump END")
|
||||
trigger.action.outText("=== dcsCommon vardump end", 30)
|
||||
env.info("=== dcsCommon vardump end")
|
||||
end
|
||||
end
|
||||
|
||||
@ -2520,8 +2590,8 @@ end
|
||||
|
||||
if not inrecursion then
|
||||
-- output a marker to find in the log / screen
|
||||
trigger.action.outText("=== dcsCommon vardump END", 30)
|
||||
--env.info("=== dcsCommon vardump END")
|
||||
trigger.action.outText("=== dcsCommon vardump end", 30)
|
||||
--env.info("=== dcsCommon vardump end")
|
||||
end
|
||||
end
|
||||
|
||||
@ -2541,15 +2611,19 @@ end
|
||||
if id == 0 then return "invalid" end
|
||||
-- translate the event id to text
|
||||
local events = {"shot", "hit", "takeoff", "land",
|
||||
"crash", "eject", "refuel", "dead",
|
||||
"crash", "eject", "refuel", "dead", -- 8
|
||||
"pilot dead", "base captured", "mission start", "mission end", -- 12
|
||||
"took control", "refuel stop", "birth", "human failure",
|
||||
"det. failure", "engine start", "engine stop", "player enter unit",
|
||||
"player leave unit", "player comment", "start shoot", "end shoot",
|
||||
"mark add", "mark changed", "makr removed", "kill",
|
||||
"score", "unit lost", "land after eject", "Paratrooper land",
|
||||
"chair discard after eject", "weapon add", "trigger zone", "landing quality mark",
|
||||
"BDA", "max"}
|
||||
"took control", "refuel stop", "birth", "human failure", -- 16
|
||||
"det. failure", "engine start", "engine stop", "player enter unit", -- 20
|
||||
"player leave unit", "player comment", "start shoot", "end shoot", -- 24
|
||||
"mark add", "mark changed", "mark removed", "kill", -- 28
|
||||
"score", "unit lost", "land after eject", "Paratrooper land", -- 32
|
||||
"chair discard after eject", "weapon add", "trigger zone", "landing quality mark", -- 36
|
||||
"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
|
||||
return events[id]
|
||||
end
|
||||
|
||||
253
modules/duel.lua
Normal file
253
modules/duel.lua
Normal 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
|
||||
@ -1,5 +1,5 @@
|
||||
guardianAngel = {}
|
||||
guardianAngel.version = "3.0.4"
|
||||
guardianAngel.version = "3.0.5"
|
||||
guardianAngel.ups = 10
|
||||
guardianAngel.name = "Guardian Angel" -- just in case someone accesses .name
|
||||
guardianAngel.launchWarning = true -- detect launches and warn pilot
|
||||
@ -61,7 +61,11 @@ guardianAngel.requiredLibs = {
|
||||
3.0.3 - monitorItem() guards against loss of target (nil)
|
||||
3.0.4 - launchSound attribute
|
||||
- interventionSound attribute
|
||||
|
||||
3.0.5 - better missiole names, their object IDs seem to have disappeared, also storing launcher name
|
||||
- msgTime to control how long warnings remain on the screen
|
||||
- disappear message now only on verbose
|
||||
- dmlZones
|
||||
|
||||
|
||||
This script detects missiles launched against protected aircraft an
|
||||
removes them when they are about to hit
|
||||
@ -133,7 +137,7 @@ end
|
||||
--
|
||||
-- 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 theTarget 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
|
||||
|
||||
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.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.tGroup = theTarget:getGroup()
|
||||
theItem.tID = theItem.tGroup:getID()
|
||||
@ -156,6 +178,7 @@ function guardianAngel.createQItem(theWeapon, theTarget, threat)
|
||||
theItem.threat = threat
|
||||
theItem.lastDesc = "(new)"
|
||||
theItem.timeStamp = timer.getTime()
|
||||
theItem.launcher = launcherName
|
||||
return theItem
|
||||
end
|
||||
|
||||
@ -222,14 +245,17 @@ function guardianAngel.monitorItem(theItem)
|
||||
if not w then return false end
|
||||
if not w:isExist() then
|
||||
--if (not theItem.missed) and (not theItem.lostTrack) then
|
||||
local desc = theItem.weaponName .. ": DISAPPEARED"
|
||||
|
||||
--[[--
|
||||
if guardianAngel.announcer and theItem.threat then
|
||||
local desc = theItem.weaponName .. ": DISAPPEARED"
|
||||
if guardianAngel.private then
|
||||
trigger.action.outTextForGroup(ID, desc, 30)
|
||||
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
|
||||
else
|
||||
trigger.action.outText(desc, 30)
|
||||
trigger.action.outText(desc, guardianAngel.msgTime)
|
||||
end
|
||||
end
|
||||
--]]--
|
||||
if guardianAngel.verbose then
|
||||
trigger.action.outText("+++gA: missile disappeared: <" .. theItem.weaponName .. ">, aimed at <" .. theItem.targetName .. ">",30)
|
||||
end
|
||||
@ -277,13 +303,13 @@ function guardianAngel.monitorItem(theItem)
|
||||
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)
|
||||
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
|
||||
if guardianAngel.launchSound then
|
||||
local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound
|
||||
trigger.action.outSoundForGroup(ID, fileName)
|
||||
end
|
||||
else
|
||||
trigger.action.outText(desc, 30)
|
||||
trigger.action.outText(desc, guardianAngel.msgTime)
|
||||
if guardianAngel.launchSound then
|
||||
local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound
|
||||
trigger.action.outSound(fileName)
|
||||
@ -340,13 +366,13 @@ function guardianAngel.monitorItem(theItem)
|
||||
|
||||
if guardianAngel.announcer then
|
||||
if guardianAngel.private then
|
||||
trigger.action.outTextForGroup(ID, desc, 30)
|
||||
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
|
||||
if guardianAngel.interventionSound then
|
||||
local fileName = "l10n/DEFAULT/" .. guardianAngel.interventionSound
|
||||
trigger.action.outSoundForGroup(ID, fileName)
|
||||
end
|
||||
else
|
||||
trigger.action.outText(desc, 30)
|
||||
trigger.action.outText(desc, guardianAngel.msgTime)
|
||||
if guardianAngel.interventionSound then
|
||||
local fileName = "l10n/DEFAULT/" .. guardianAngel.interventionSound
|
||||
trigger.action.outSound(fileName)
|
||||
@ -375,9 +401,9 @@ function guardianAngel.monitorItem(theItem)
|
||||
|
||||
if guardianAngel.announcer then
|
||||
if guardianAngel.private then
|
||||
trigger.action.outTextForGroup(ID, desc, 30)
|
||||
trigger.action.outTextForGroup(ID, desc, guardianAngel.msgTime)
|
||||
else
|
||||
trigger.action.outText(desc, 30)
|
||||
trigger.action.outText(desc, guardianAngel.msgTime)
|
||||
end
|
||||
end
|
||||
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
|
||||
local targetName = theTarget:getName()
|
||||
local watchedUnit = guardianAngel.getWatchedUnitByName(targetName)
|
||||
local launcher = theUnit
|
||||
guardianAngel.missilesAndTargets[theWeapon:getName()] = targetName
|
||||
if not watchedUnit then
|
||||
-- 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)
|
||||
end
|
||||
-- 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)
|
||||
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, true) -- this is watched
|
||||
local theQItem = guardianAngel.createQItem(theWeapon, theTarget, true, launcher) -- this is watched
|
||||
table.insert(guardianAngel.missilesInTheAir, theQItem)
|
||||
guardianAngel.invokeCallbacks("launch", theQItem.targetName, theQItem.weaponName)
|
||||
|
||||
@ -624,13 +651,13 @@ function guardianAngel.somethingHappened(event)
|
||||
-- currently, we always detect immediately
|
||||
-- can be moved to update()
|
||||
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
|
||||
local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound
|
||||
trigger.action.outSoundForGroup(grpID, fileName)
|
||||
end
|
||||
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
|
||||
local fileName = "l10n/DEFAULT/" .. guardianAngel.launchSound
|
||||
trigger.action.outSound(fileName)
|
||||
@ -847,40 +874,42 @@ function guardianAngel.readConfigZone()
|
||||
end
|
||||
|
||||
|
||||
guardianAngel.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
guardianAngel.verbose = theZone:getBoolFromZoneProperty("verbose", false)
|
||||
|
||||
guardianAngel.autoAddPlayer = cfxZones.getBoolFromZoneProperty(theZone, "autoAddPlayer", true)
|
||||
guardianAngel.launchWarning = cfxZones.getBoolFromZoneProperty(theZone, "launchWarning", true)
|
||||
guardianAngel.intervention = cfxZones.getBoolFromZoneProperty(theZone, "intervention", true)
|
||||
guardianAngel.announcer = cfxZones.getBoolFromZoneProperty(theZone, "announcer", true)
|
||||
guardianAngel.private = cfxZones.getBoolFromZoneProperty(theZone, "private", false)
|
||||
guardianAngel.explosion = cfxZones.getNumberFromZoneProperty(theZone, "explosion", -1)
|
||||
guardianAngel.fxDistance = cfxZones.getNumberFromZoneProperty(theZone, "fxDistance", 500)
|
||||
guardianAngel.autoAddPlayer = theZone:getBoolFromZoneProperty("autoAddPlayer", true)
|
||||
guardianAngel.launchWarning = theZone:getBoolFromZoneProperty("launchWarning", true)
|
||||
guardianAngel.intervention = theZone:getBoolFromZoneProperty("intervention", true)
|
||||
guardianAngel.announcer = theZone:getBoolFromZoneProperty( "announcer", true)
|
||||
guardianAngel.private = theZone:getBoolFromZoneProperty("private", false)
|
||||
guardianAngel.explosion = theZone:getNumberFromZoneProperty("explosion", -1)
|
||||
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.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)
|
||||
guardianAngel.msgTime = theZone:getNumberFromZoneProperty("msgTime", 30)
|
||||
|
||||
if theZone:hasProperty("activate?") then
|
||||
guardianAngel.activate = theZone:getStringFromZoneProperty("activate?", "*<none>")
|
||||
guardianAngel.lastActivate = theZone:getFlagValue(guardianAngel.activate)
|
||||
elseif theZone:hasProperty("on?") then
|
||||
guardianAngel.activate = theZone:getStringFromZoneProperty("on?", "*<none>")
|
||||
guardianAngel.lastActivate = theZone:getFlagValue(guardianAngel.activate)
|
||||
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)
|
||||
if theZone:hasProperty("deactivate?") then
|
||||
guardianAngel.deactivate = theZone:getStringFromZoneProperty("deactivate?", "*<none>")
|
||||
guardianAngel.lastDeActivate = theZone:getFlagValue(guardianAngel.deactivate)
|
||||
elseif theZone:hasProperty("off?") then
|
||||
guardianAngel.deactivate = theZone:getStringFromZoneProperty("off?", "*<none>")
|
||||
guardianAngel.lastDeActivate = theZone:getFlagValue(guardianAngel.deactivate)
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "launchSound") then
|
||||
guardianAngel.launchSound = cfxZones.getStringFromZoneProperty(theZone, "launchSound", "nosound")
|
||||
if theZone:hasProperty("launchSound") then
|
||||
guardianAngel.launchSound = theZone:getStringFromZoneProperty("launchSound", "nosound")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "interventionSound") then
|
||||
guardianAngel.interventionSound = cfxZones.getStringFromZoneProperty(theZone, "interventionSound", "nosound")
|
||||
if theZone:hasProperty("interventionSound") then
|
||||
guardianAngel.interventionSound = theZone:getStringFromZoneProperty("interventionSound", "nosound")
|
||||
end
|
||||
|
||||
guardianAngel.configZone = theZone
|
||||
@ -894,7 +923,7 @@ end
|
||||
--
|
||||
|
||||
function guardianAngel.processGuardianZone(theZone)
|
||||
theZone.angelic = cfxZones.getBoolFromZoneProperty(theZone, "guardian", true)
|
||||
theZone.angelic = theZone:getBoolFromZoneProperty("guardian", true)
|
||||
|
||||
|
||||
if theZone.verbose or guardianAngel.verbose then
|
||||
@ -947,7 +976,7 @@ function guardianAngel.start()
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
-- go go go
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
unitZone={}
|
||||
unitZone.version = "1.2.4"
|
||||
unitZone.version = "1.2.5"
|
||||
unitZone.verbose = false
|
||||
unitZone.ups = 1
|
||||
unitZone.requiredLibs = {
|
||||
@ -17,6 +17,7 @@ unitZone.requiredLibs = {
|
||||
1.2.3 - better guards for enterZone!, exitZone!, changeZone!
|
||||
- better guards for uzOn? and uzOff?
|
||||
1.2.4 - more verbosity on uzDirect
|
||||
1.2.5 - reading config improvement
|
||||
|
||||
--]]--
|
||||
|
||||
@ -196,8 +197,9 @@ function unitZone.checkZoneStatus(theZone)
|
||||
local playerCheck = theZone.matching == "player"
|
||||
if playerCheck then
|
||||
-- we check the names for players only
|
||||
-- collector holds units, not groups
|
||||
for idx, pUnit in pairs(theGroups) do
|
||||
local puName=pUnit:getName()
|
||||
local puName = pUnit:getName()
|
||||
local hasMatch = false
|
||||
if theZone.lookForBeginsWith then
|
||||
hasMatch = dcsCommon.stringStartsWith(puName, lookFor)
|
||||
@ -323,10 +325,7 @@ end
|
||||
function unitZone.readConfigZone()
|
||||
local theZone = cfxZones.getZoneByName("unitZoneConfig")
|
||||
if not theZone then
|
||||
if unitZone.verbose then
|
||||
trigger.action.outText("+++uZne: NO config zone!", 30)
|
||||
end
|
||||
return
|
||||
theZone = cfxZones.createSimpleZone("unitZoneConfig")
|
||||
end
|
||||
|
||||
unitZone.verbose = cfxZones.getBoolFromZoneProperty(theZone, "verbose", false)
|
||||
|
||||
BIN
tutorial & demo missions/demo - Civ Air International.miz
Normal file
BIN
tutorial & demo missions/demo - Civ Air International.miz
Normal file
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user