mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
760 lines
26 KiB
Lua
760 lines
26 KiB
Lua
civAir = {}
|
|
civAir.version = "3.0.0"
|
|
--[[--
|
|
1.0.0 initial version
|
|
1.1.0 exclude list for airfields
|
|
1.1.1 bug fixes with remove flight
|
|
and betweenHubs
|
|
check if slot is really free before spawning
|
|
add overhead waypoint
|
|
1.1.2 inAir start possible
|
|
1.2.0 civAir can use own config file
|
|
1.2.1 slight update to config file (moved active/idle)
|
|
1.3.0 add ability to use zones to add closest airfield to
|
|
trafficCenters or excludeAirfields
|
|
1.4.0 ability to load config from zone to override
|
|
all configs it finds
|
|
module check
|
|
removed obsolete civAirConfig module
|
|
1.5.0 process zones as in all other modules
|
|
verbose is part of config zone
|
|
reading type array from config corrected
|
|
massive simplifications: always between zoned airfieds
|
|
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
|
|
3.0.0 liveries support
|
|
default liveries for Yak-50 (main test case)
|
|
default liveries for C-130, c-17A, IL-76MD, An-30M, An-26B
|
|
default aircraft types dcs
|
|
support for CAM
|
|
default liveries for all CAM types
|
|
new DCS attribute
|
|
new CAM attribute
|
|
deafault to one Yak-40 if neither
|
|
support for 'civil_liveries' zone
|
|
|
|
--]]--
|
|
|
|
civAir.ups = 0.05 -- updates per second. 0.05 = once every 20 seconds
|
|
civAir.initialAirSpawns = true -- when true has population spawn in-air at start
|
|
civAir.verbose = false
|
|
|
|
civAir.aircraftTypes = {}
|
|
civAir.dcsBuiltinTypes = {"Yak-40", "C-130", "C-17A", "IL-76MD", "An-30M", "An-26B"}
|
|
civAir.CAMTypes = { "A_320", "A_330", "A_380", "B_727", "B_737", "B_747", "B_757", "Cessna_210N", "DC_10",}
|
|
|
|
civAir.liveries = {
|
|
|
|
-- definitions for plain vanilla DCS
|
|
["Yak-40"] = {"Aeroflot", "Algeria GLAM", "Olympic Airways", "Ukranian", "Georgian Airlines", }, --"Georgian Airlines",
|
|
["C-130"] = {"Air Algerie L-382 White", "Algerian AF Green", "Algerian AF H30 White", "Belgian Air Force", "Canada's Air Force", "French Air Force", "HAF gray", "IRIAF 5-8503", "IRIAF 5-8518", "Israel Defence Force", "Royal Air Force", "Royal Danish Air Force", "Royal Netherlands Air Force", "Royal Norwegian Air Force", "Spanish Air Force", "Turkish Air Force", "US Air Force", },
|
|
["C-17A"] = {"usaf standard", },
|
|
["IL-76MD"] = {"Algerian AF IL-76MD", "China Air Force New", "China Air Force Old", "FSB aeroflot", "MVD aeroflot", "RF Air Force", "Ukrainian AF", "Ukrainian AF aeroflot", },
|
|
["An-30M"] = {"15th Transport AB", "China CAAC", "RF Air Force"},
|
|
["An-26B"] = {"Abkhazian AF", "Aeroflot", "China PLAAF", "Georgian AF", "RF Air Force", "RF Navy", "Ukraine AF", },
|
|
|
|
-- definitions for CAM mod
|
|
["A_320"] = {"Aeroflot", "Aeroflot 1", "Air Asia", "Air Berlin", "Air Berlin FFO", "Air Berlin OLT", "Air Berlin retro", "Air France", "Air Moldova", "Airbus Neo", "Al Maha", "Alitalia", "American Airlines", "British Airways", "Cebu Pacific", "Clean", "Condor", "Delta Airlines", "Easy Jet", "Easy Jet Berlin", "Easy Jet w", "Edelweiss", "Emirates", "Etihad", "Eurowings", "Eurowings BVB09", "Eurowings Europa Park", "Fly Georgia", "Fly Niki", "Frontier", "German Wings", "Gulf Air", "Iberia", "Iran Air", "Jet Blue NY", "JetBlue", "jetBlue FDNY", "Kish Air", "Kuwait Airways", "Lufthansa", "Lufthansa New", "MEA", "MRTT Canada", "MRTT Luftwaffe", "Qatar", "RAF MPA", "RAF VIP", "S7", "SAS", "Saudi Gulf", "Saudia", "Small Planet", "Star Alliance", "SWISS", "Thomas Cook", "Tunis Air", "Turkish Airlines", "United", "Ural Airlines", "US Airways", "Vietnam Airlines", "Virgin", "WiZZ", "WiZZ Budapest", "WOW", },
|
|
|
|
["A_330"] = {"Aer Lingus", "Aeroflot", "Air Canada", "Air China", "Air Tahiti Nui", "AirAsia", "Airbus", "BOURKHAN", "Brussels Airline", "Cathay Pacific", "CEBU Pacific", "China Eastern", "Clean", "DELTA", "DragonAir", "Edelweiss", "Egypt Air", "Emirates", "ETIHAD", "EVA", "FIJI", "FinnAir", "FrenchBlue", "Garude Indunesia", "GulfAir", "Hainan Airlines", "Iberia", "IRoI", "KLM", "LAN Airways", "Lion Air PK-LEG", "LTU", "Lufthansa", "NWA", "nwaold", "Olympic", "OmanAir", "Orbit", "Philipines", "Qantas", "Qatar", "RAF Voyager", "Singapore", "Skyteam", "Srilankan", "Star Aliance", "Swiss", "Thomas Cook", "Turkish Airlines", "US Airways", "Virgin Atlantic", "WorldTrave", },
|
|
|
|
["A_380"] = {"Air France", "BA", "China Southern", "Clean", "Emirates", "KA", "LH", "LHF", "Qantas Airways", "QTR", "SA", "TA", },
|
|
|
|
["B_727"] = {"AEROFLOT", "Air France", "Alaska", "Alitalia", "American Airlines", "Clean", "Delta Airlines", "Delta Airlines OLD", "FedEx", "Hapag Lloyd", "Lufthansa", "Lufthansa Oberhausen Old", "Northwest", "Pan Am", "Singapore Airlines", "Southwest", "UNITED", "UNITED Old", "ZERO G", },
|
|
|
|
["B_737"] = {"Air Algerie", "Air Berlin", "Air France", "airBaltic", "Airzena", "AM", "American_Airlines", "British Airways", "C40s", "Clean", "Disney", "EA", "easyJet", "FINNAIR", "HARIBO", "JA", "Jet2", "kulula", "LH", "Lufthansa BA", "Lufthansa KR", "OLD_BA", "OMAN AIR", "P8 RAF", "P8 USN", "PAN AM", "Polskie Linie Lotnicze LOT", "QANTAS", "RYANAIR", "SouthWest Lone Star", "ThomsonFly", "TNT", "Ukraine Airlines", "UPS", },
|
|
|
|
["B_747"] = {"AF", "AF-One", "AI", "CP", "IM", "KLM", "LH", "NW", "PA", "QA", "TA", },
|
|
|
|
["B_757"] = {"AA", "BA", "C-32", "Delta", "DHL", "easyJet", "Swiss", "Thomson", },
|
|
|
|
["Cessna_210N"] = {"Blank", "D-EKVW", "HellenicAF", "Muster", "N9572H", "SEagle blue", "SEagle red", "USAF-Academy", "V5-BUG", "VH-JGA", },
|
|
|
|
["DC_10"] = {"SWISSAIR HB-IHL", "SWISSAIR HB-IMC", "SWISSAIR HB-IPF", }
|
|
}
|
|
civAir.maxTraffic = 10 -- number of flights at the same time
|
|
civAir.maxIdle = 8 * 60 -- seconds of ide time before it is removed after landing
|
|
|
|
civAir.trafficCenters = {}
|
|
civAir.excludeAirfields = {}
|
|
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
|
|
"cfxZones", -- zones management foc CSAR and CSAR Mission zones
|
|
}
|
|
|
|
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
|
|
theZone = cfxZones.createSimpleZone("civAirConfig")
|
|
end
|
|
civAir.verbose = theZone.verbose
|
|
civAir.ups = theZone:getNumberFromZoneProperty("ups", 0.05)
|
|
if civAir.ups < .0001 then civAir.ups = 0.05 end
|
|
|
|
|
|
if theZone:hasProperty("maxTraffic") then
|
|
civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxTraffic", 10)
|
|
else --if theZone:hasProperty("maxFlights") then
|
|
civAir.maxTraffic = theZone:getNumberFromZoneProperty( "maxFlights", 10)
|
|
end
|
|
|
|
civAir.maxIdle = theZone:getNumberFromZoneProperty("maxIdle", 8 * 60)
|
|
|
|
civAir.initialAirSpawns = theZone:getBoolFromZoneProperty( "initialAirSpawns", true)
|
|
|
|
civAir.owner = theZone:getNumberFromZoneProperty("owner", 82) -- default to UN peacekeepers
|
|
-- build my aircraft types list
|
|
local hasDCS = theZone:getBoolFromZoneProperty("dcs", true)
|
|
if hasDCS then
|
|
if civAir.verbose then trigger.action.outText("+++civA: adding DCS standard types", 30) end
|
|
for idx, aType in pairs(civAir.dcsBuiltinTypes) do
|
|
table.insert(civAir.aircraftTypes, aType)
|
|
end
|
|
end
|
|
local hasCAM = theZone:getBoolFromZoneProperty("cam", false)
|
|
if hasCAM then
|
|
if civAir.verbose then trigger.action.outText("+++civA: adding CAM add-on types", 30) end
|
|
for idx, aType in pairs(civAir.CAMTypes) do
|
|
table.insert(civAir.aircraftTypes, aType)
|
|
end
|
|
end
|
|
|
|
-- now get types and liveries from 'civil_liveries' if present
|
|
local livZone = cfxZones.getZoneByName("civil_liveries")
|
|
if livZone then
|
|
if civAir.verbose then
|
|
trigger.action.outText("civA: found and processing 'civil_liveries' zone data.", 30)
|
|
end
|
|
|
|
-- read all into my types registry, replacing whatever is there
|
|
local rawLiver = cfxZones.getAllZoneProperties(livZone)
|
|
local newTypes, newLiveries = civAir.addTypesAndLiveries(rawLiver)
|
|
-- now types to existing types if not already there
|
|
for idx, aType in pairs(newTypes) do
|
|
dcsCommon.addToTableIfNew(civAir.aircraftTypes, aType)
|
|
if civAir.verbose then
|
|
trigger.action.outText("+++civA: processed and added aircraft <" .. aType .. "> to civAir", 30)
|
|
end
|
|
end
|
|
-- now replace liveries or add if not already there
|
|
for aType, liveries in pairs(newLiveries) do
|
|
civAir.liveries[aType] = liveries
|
|
if civAir.verbose then
|
|
trigger.action.outText("+++civA: replaced/added liveries for aircraft <" .. aType .. ">", 30)
|
|
end
|
|
end
|
|
end
|
|
|
|
if #civAir.aircraftTypes < 1 then
|
|
table.insert(civAir.aircraftTypes, "Yak-40")
|
|
if civAir.verbose then
|
|
trigger.action.outText("+++civA: adding singular Yak-40", 30)
|
|
end
|
|
end
|
|
|
|
-- selective types, overwrites existing types when present
|
|
-- also provides legacy support
|
|
if theZone:hasProperty("aircraftTypes") then
|
|
local theTypes = theZone:getStringFromZoneProperty( "aircraftTypes", civAir.aircraftTypes)
|
|
local typeArray = dcsCommon.splitString(theTypes, ",")
|
|
typeArray = dcsCommon.trimArray(typeArray)
|
|
civAir.aircraftTypes = typeArray
|
|
if civAir.verbose then
|
|
trigger.action.outText("+++civA: setting aircraft types to <" .. theTypes .. ">", 30)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
function civAir.addTypesAndLiveries(rawIn)
|
|
local newTypes = {}
|
|
local newLiveries = {}
|
|
-- now iterate the input table, and generate new types and
|
|
-- liveries from it
|
|
for theType, liveries in pairs (rawIn) do
|
|
if civAir.verbose then
|
|
trigger.action.outText("+++civA: processing type <" .. theType .. ">:<" .. liveries .. ">", 30)
|
|
end
|
|
local livA = dcsCommon.splitString(liveries, ',')
|
|
livA = dcsCommon.trimArray(livA)
|
|
table.insert(newTypes, theType)
|
|
newLiveries[theType] = livA
|
|
end
|
|
|
|
return newTypes, newLiveries
|
|
end
|
|
|
|
|
|
function civAir.processZone(theZone)
|
|
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" or value == "closed" then
|
|
table.insert(civAir.excludeAirfields, afName)
|
|
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
|
|
elseif dcsCommon.stringStartsWith(value, "outb") then
|
|
table.insert(civAir.landingOnly, inoutName)
|
|
civAir.inoutZones[inoutName] = theZone
|
|
elseif dcsCommon.stringStartsWith(value, "in/out") then
|
|
table.insert(civAir.trafficCenters, inoutName)
|
|
civAir.inoutZones[inoutName] = theZone
|
|
else
|
|
table.insert(civAir.trafficCenters, afName) -- note that adding the same twice makes it more likely to be picked
|
|
end
|
|
else
|
|
trigger.action.outText("+++civA: unable to resolve airfield for <" .. theZone.name .. ">", 30)
|
|
end
|
|
end
|
|
|
|
|
|
function civAir.addPlane(thePlaneUnit) -- warning: is actually a group
|
|
if not thePlaneUnit then return end
|
|
civAir.activePlanes[thePlaneUnit:getName()] = thePlaneUnit
|
|
end
|
|
|
|
function civAir.removePlaneGroupByName(aName)
|
|
if not aName then
|
|
return
|
|
end
|
|
if civAir.activePlanes[aName] then
|
|
civAir.activePlanes[aName] = nil
|
|
else
|
|
if civAir.verbose then
|
|
trigger.action.outText("civA: warning - ".. aName .." remove req but not found", 30)
|
|
end
|
|
end
|
|
end
|
|
|
|
function civAir.removePlane(thePlaneUnit) -- warning: is actually a group
|
|
if not thePlaneUnit then return end
|
|
if not thePlaneUnit:isExist() then return end
|
|
civAir.activePlanes[thePlaneUnit:getName()] = nil
|
|
end
|
|
|
|
function civAir.getPlane(aName) -- warning: returns GROUP!
|
|
return civAir.activePlanes[aName]
|
|
end
|
|
|
|
|
|
function civAir.filterAirfields(inAll, inFilter)
|
|
local outList = {}
|
|
for idx, anItem in pairs(inAll) do
|
|
if dcsCommon.arrayContainsString(inFilter, anItem) then
|
|
-- filtered, do nothing.
|
|
else
|
|
-- not filtered
|
|
table.insert(outList, anItem)
|
|
end
|
|
end
|
|
return outList
|
|
end
|
|
|
|
function civAir.getTwoAirbases()
|
|
local fAB -- first airbase to depart
|
|
local sAB -- second airbase to fly to
|
|
|
|
local departAB = dcsCommon.combineTables(civAir.trafficCenters, civAir.departOnly)
|
|
-- remove all currently excluded air bases from departure
|
|
local filteredAB = civAir.filterAirfields(departAB, civAir.excludeAirfields)
|
|
-- if none left, error
|
|
if #filteredAB < 1 then
|
|
trigger.action.outText("+++civA: too few departure airfields", 30)
|
|
return nil, nil
|
|
end
|
|
|
|
-- now pick the departure airfield
|
|
fAB = dcsCommon.pickRandom(filteredAB)
|
|
|
|
-- now generate list of landing airfields
|
|
local arriveAB = dcsCommon.combineTables(civAir.trafficCenters, civAir.landingOnly)
|
|
-- remove all currently excluded air bases from arrival
|
|
filteredAB = civAir.filterAirfields(arriveAB, civAir.excludeAirfields)
|
|
|
|
-- if one left use it twice, boring flight.
|
|
if #filteredAB < 1 then
|
|
trigger.action.outText("+++civA: too few arrival airfields", 30)
|
|
return nil, nil
|
|
end
|
|
|
|
-- pick any second that are not the same
|
|
local tries = 0
|
|
repeat
|
|
sAB = dcsCommon.pickRandom(filteredAB)
|
|
tries = tries + 1 -- only try 10 times
|
|
until fAB ~= sAB or tries > 10
|
|
|
|
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
|
|
|
|
function civAir.parkingIsFree(fromWP)
|
|
-- iterate over all currently registres flights and make
|
|
-- sure that their location isn't closer than 10m to my new parking
|
|
local loc = {}
|
|
loc.x = fromWP.x
|
|
loc.y = fromWP.alt
|
|
loc.z = fromWP.z
|
|
|
|
for name, aPlaneGroup in pairs(civAir.activePlanes) do
|
|
if Group.isExist(aPlaneGroup) then
|
|
local aPlane = aPlaneGroup:getUnit(1)
|
|
if aPlane and Unit.isExist(aPlane) then
|
|
pos = aPlane:getPoint()
|
|
local delta = dcsCommon.dist(loc, pos)
|
|
if delta < 21 then
|
|
-- way too close
|
|
trigger.action.outText("civA: too close for comfort - " .. aPlane:getName() .. " occupies my slot", 30)
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
civAir.airStartSeparation = 0
|
|
function civAir.createFlight(name, theTypeString, fromAirfield, toAirfield, inAirStart)
|
|
if not fromAirfield then
|
|
trigger.action.outText("civA: NIL source", 30)
|
|
return nil
|
|
end
|
|
|
|
if not toAirfield then
|
|
trigger.action.outText("civA: NIL destination", 30)
|
|
return nil
|
|
end
|
|
|
|
local randomizeLoc = inAirStart
|
|
|
|
local theGroup = dcsCommon.createEmptyAircraftGroupData (name)
|
|
local theAUnit = dcsCommon.createAircraftUnitData(name .. "-GA", theTypeString, false)
|
|
-- add livery capability for this aircraft
|
|
civAir.processLiveriesFor(theAUnit, theTypeString)
|
|
|
|
-- enforce civ attribute
|
|
theAUnit.civil_plane = true
|
|
|
|
theAUnit.payload.fuel = 100000
|
|
dcsCommon.addUnitToGroupData(theAUnit, theGroup)
|
|
|
|
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
|
|
end
|
|
if inAirStart then
|
|
-- modify WP into an in-air point
|
|
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
|
|
|
|
-- 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)
|
|
if not zoneApproach then
|
|
dcsCommon.addRoutePointForGroupData(theGroup, overheadWP)
|
|
end
|
|
dcsCommon.addRoutePointForGroupData(theGroup, toWP)
|
|
|
|
-- spawn
|
|
local theSpawnedGroup = coalition.addGroup(civAir.owner, groupCat, theGroup) -- 82 is UN peacekeepers
|
|
if zoneApproach then
|
|
-- track this flight to target zone
|
|
civAir.outboundFlights[name] = zoneApproach
|
|
end
|
|
return theSpawnedGroup
|
|
end
|
|
|
|
-- flightCount is a global that holds the number of flights we track
|
|
civAir.flightCount = 0
|
|
function civAir.createNewFlight(inAirStart)
|
|
|
|
civAir.flightCount = civAir.flightCount + 1
|
|
local fAB, sAB = civAir.getTwoAirbases() -- from AB
|
|
if not fAB or not sAB then
|
|
trigger.action.outText("+++civA: cannot create flight, no source or destination", 30)
|
|
return
|
|
end
|
|
|
|
-- 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)
|
|
|
|
if not theFlight then
|
|
-- flight was not able to spawn.
|
|
trigger.action.outText("civA: aborted civ spawn on fAB:" .. fAB:getName(), 30)
|
|
return
|
|
end
|
|
|
|
civAir.addPlane(theFlight) -- track it
|
|
|
|
if civAir.verbose then
|
|
trigger.action.outText("civA: created flight from <" .. fAB.name .. "> to <" .. sAB.name .. ">", 30)
|
|
end
|
|
end
|
|
|
|
function civAir.airStartPopulation()
|
|
local numAirStarts = civAir.maxTraffic / 2
|
|
civAir.airStartSeparation = 0
|
|
while numAirStarts > 0 do
|
|
numAirStarts = numAirStarts - 1
|
|
civAir.airStartSeparation = civAir.airStartSeparation + 200
|
|
civAir.createNewFlight(true)
|
|
end
|
|
-- start update in 15 seconds
|
|
timer.scheduleFunction(civAir.update, {}, timer.getTime() + 15)
|
|
end
|
|
|
|
--
|
|
-- Livery handling
|
|
--
|
|
|
|
function civAir.processLiveriesFor(theData, theType)
|
|
if civAir.liveries[theType] then
|
|
local available = civAir.liveries[theType]
|
|
local chosen = dcsCommon.pickRandom(available)
|
|
theData.livery_id = chosen
|
|
end
|
|
end
|
|
|
|
--
|
|
-- 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)
|
|
|
|
-- clean-up first:
|
|
-- any group that no longer exits will be removed from the array
|
|
local removeMe = {}
|
|
for name, group in pairs (civAir.activePlanes) do
|
|
if not group:isExist() then
|
|
table.insert(removeMe, name) -- mark for deletion
|
|
--Group.destroy(group) -- may break
|
|
end
|
|
end
|
|
|
|
for idx, name in pairs(removeMe) do
|
|
civAir.activePlanes[name] = nil
|
|
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()
|
|
for name, aPlaneGroup in pairs(civAir.activePlanes) do
|
|
local speed = 0
|
|
if aPlaneGroup:isExist() then
|
|
local aPlane = aPlaneGroup:getUnit(1)
|
|
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, plane no longer exists
|
|
civAir.idlePlanes[name] = -1000
|
|
speed = 0
|
|
end
|
|
else
|
|
-- force removal, group no longer exists
|
|
civAir.idlePlanes[name] = -1000
|
|
speed = 0
|
|
end
|
|
|
|
if speed < 0.5 then
|
|
if not civAir.idlePlanes[name] then
|
|
civAir.idlePlanes[name] = now
|
|
end
|
|
local idleTime = now - civAir.idlePlanes[name]
|
|
--trigger.action.outText("civA: Idling <" .. name .. "> for t=" .. idleTime, 30)
|
|
if idleTime > civAir.maxIdle then
|
|
table.insert(overduePlanes, name)
|
|
end
|
|
else
|
|
-- zero out idle plane, it's moving fast enough
|
|
civAir.idlePlanes[name] = nil
|
|
end
|
|
end
|
|
|
|
-- see if we have less than max flights running
|
|
if planeNum < civAir.maxTraffic then
|
|
-- spawn a new plane. just one per pass
|
|
civAir.createNewFlight()
|
|
end
|
|
|
|
-- now remove all planes that are overdue
|
|
for idx, aName in pairs(overduePlanes) do
|
|
local aFlight = civAir.getPlane(aName) -- returns a group
|
|
civAir.removePlaneGroupByName(aName) -- remove from roster
|
|
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
|
|
|
|
|
|
|
|
function civAir.doDebug(any)
|
|
trigger.action.outText("cf/x civTraffic debugger.", 30)
|
|
local desc = "Active Planes:"
|
|
local now = timer.getTime()
|
|
for name, group in pairs (civAir.activePlanes) do
|
|
desc = desc .. "\n" .. name
|
|
if civAir.idlePlanes[name] then
|
|
delay = now - civAir.idlePlanes[name]
|
|
desc = desc .. " (idle for " .. delay .. ")"
|
|
end
|
|
end
|
|
trigger.action.outText(desc, 30)
|
|
end
|
|
|
|
function civAir.collectHubs()
|
|
local pZones = cfxZones.zonesWithProperty("civAir")
|
|
|
|
for k, aZone in pairs(pZones) do
|
|
civAir.processZone(aZone)
|
|
end
|
|
end
|
|
|
|
function civAir.listTrafficCenters()
|
|
trigger.action.outText("Traffic Centers", 30)
|
|
for idx, aName in pairs(civAir.trafficCenters) do
|
|
trigger.action.outText(aName, 30)
|
|
end
|
|
|
|
if #civAir.departOnly > 0 then
|
|
trigger.action.outText("Departure-Only:", 30)
|
|
for idx, aName in pairs(civAir.departOnly) do
|
|
trigger.action.outText(aName, 30)
|
|
end
|
|
end
|
|
|
|
if #civAir.landingOnly > 0 then
|
|
trigger.action.outText("Arrival/Landing-Only:", 30)
|
|
for idx, aName in pairs(civAir.landingOnly) do
|
|
trigger.action.outText(aName, 30)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- start
|
|
function civAir.start()
|
|
-- module check
|
|
if not dcsCommon.libCheck("cfx civAir", civAir.requiredLibs) then
|
|
return false
|
|
end
|
|
|
|
-- see if there is a config zone and load it
|
|
civAir.readConfigZone()
|
|
|
|
-- look for zones to add to air fields list
|
|
civAir.collectHubs()
|
|
|
|
-- make sure there is something in trafficCenters
|
|
if (#civAir.trafficCenters + #civAir.departOnly < 1) or
|
|
(#civAir.trafficCenters + #civAir.landingOnly < 1)
|
|
then
|
|
trigger.action.outText("+++civA: auto-populating", 30)
|
|
-- simply add airfields on the map
|
|
local allBases = dcsCommon.getAirbasesWhoseNameContains("*", 0)
|
|
for idx, aBase in pairs(allBases) do
|
|
local afName = aBase:getName()
|
|
|
|
table.insert(civAir.trafficCenters, afName)
|
|
end
|
|
end
|
|
|
|
if civAir.verbose then
|
|
civAir.listTrafficCenters()
|
|
end
|
|
|
|
-- air-start half population if allowed
|
|
-- allow mission 15 seconds to settle before we start populating to
|
|
-- allow better access to liveries
|
|
if civAir.initialAirSpawns then
|
|
timer.scheduleFunction(civAir.airStartPopulation, {}, timer.getTime() + 5)
|
|
else
|
|
-- start update in 15 seconds
|
|
timer.scheduleFunction(civAir.update, {}, timer.getTime() + 15)
|
|
end
|
|
|
|
-- start outbound tracking
|
|
civAir.trackOutbound()
|
|
|
|
-- say hi!
|
|
trigger.action.outText("cf/x civAir v" .. civAir.version .. " started.", 30)
|
|
return true
|
|
end
|
|
|
|
if not civAir.start() then
|
|
trigger.action.outText("cf/x civAir aborted: missing libraries", 30)
|
|
civAir = nil
|
|
end
|
|
|
|
--[[--
|
|
Additional ideas
|
|
|
|
- callbacks for civ spawn / despawn
|
|
- add civkill callback / redCivKill blueCivKill flag bangers
|
|
- Helicopter support
|
|
- 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
|
|
|
|
--]]-- |