MOOSE/Moose Development/Moose/Ops/EasyGCICAP.lua

1625 lines
62 KiB
Lua

-------------------------------------------------------------------------
-- Easy CAP/GCI Class, based on OPS classes
-------------------------------------------------------------------------
--
-- ## Documentation:
--
-- https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.EasyGCICAP.html
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/EasyGCICAP).
--
-------------------------------------------------------------------------
-- Date: September 2023
-- Last Update: Aug 2025
-------------------------------------------------------------------------
--
--- **Ops** - Easy GCI & CAP Manager
--
-- ===
--
-- **Main Features:**
--
-- * Automatically create and manage A2A CAP/GCI defenses using an AirWing and Squadrons for one coalition
-- * Easy set-up
-- * Add additional AirWings on other airbases
-- * Each wing can have more than one Squadron - tasking to Squadrons is done on a random basis per AirWing
-- * Create borders and zones of engagement
-- * Detection can be ground based and/or via AWACS
--
-- ===
--
-- ### AUTHOR: **applevangelist**
--
-- @module Ops.EasyGCICAP
-- @image AI_Combat_Air_Patrol.JPG
--- EASYGCICAP Class
-- @type EASYGCICAP
-- @field #string ClassName
-- @field #number overhead
-- @field #number engagerange
-- @field #number capgrouping
-- @field #string airbasename
-- @field Wrapper.Airbase#AIRBASE airbase
-- @field #number coalition
-- @field #string alias
-- @field #table wings
-- @field Ops.Intel#INTEL Intel
-- @field #number resurrection
-- @field #number capspeed
-- @field #number capalt
-- @field #number capdir
-- @field #number capleg
-- @field #number maxinterceptsize
-- @field #number missionrange
-- @field #number noalert5
-- @field #table ManagedAW
-- @field #table ManagedSQ
-- @field #table ManagedCP
-- @field #table ManagedTK
-- @field #table ManagedEWR
-- @field #table ManagedREC
-- @field #number MaxAliveMissions
-- @field #boolean debug
-- @field #number repeatsonfailure
-- @field Core.Set#SET_ZONE GoZoneSet
-- @field Core.Set#SET_ZONE NoGoZoneSet
-- @field Core.Set#SET_ZONE ConflictZoneSet
-- @field #boolean Monitor
-- @field #boolean TankerInvisible
-- @field #number CapFormation
-- @field #table ReadyFlightGroups
-- @field #boolean DespawnAfterLanding
-- @field #boolean DespawnAfterHolding
-- @field #list<Ops.Auftrag#AUFTRAG> ListOfAuftrag
-- @field #string defaulttakeofftype Take off type
-- @field #number FuelLowThreshold
-- @field #number FuelCriticalThreshold
-- @field #boolean showpatrolpointmarks
-- @extends Core.Fsm#FSM
--- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown.
--
-- ===
--
-- # The EasyGCICAP Concept
--
-- The idea of this class is partially to make the OPS classes easier operational for an A2A CAP/GCI defense network, and to replace the legacy AI_A2A_Dispatcher system - not to it's
-- full extent, but make a basic system work very quickly.
--
-- # Setup
--
-- ## Basic understanding
--
-- The basics are, there is **one** and only **one** AirWing per airbase. Each AirWing has **at least** one Squadron, who will do both CAP and GCI tasks. Squadrons will be randomly chosen for the task at hand.
-- Each AirWing has **at least** one CAP Point that it manages. CAP Points will be covered by the AirWing automatically as long as airframes are available. Detected intruders will be assigned to **one**
-- AirWing based on proximity (that is, if you have more than one).
--
-- ## Assignment of tasks for intruders
--
-- Either a CAP Plane or a newly spawned GCI plane will take care of the intruders. Standard overhead is 0.75, i.e. a group of 3 intrudes will
-- be managed by 2 planes from the assigned AirWing. There is an maximum missions limitation per AirWing, so we do not spam the skies.
--
-- ## Basic set-up code
--
-- ### Prerequisites
--
-- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!**
-- E.g. for Kuitaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ.
-- Next put a late activated template group for your CAP/GCI Squadron on the map. Last, put a zone on the map for the CAP operations, let's name it "Blue Zone 1". Size of the zone plays no role.
-- Put an EW radar system on the map and name it aptly, like "Blue EWR".
--
-- ### Zones
--
-- For our example, you create a RED and a BLUE border, as a closed polygonal zone representing the borderlines. You can also have conflict zone, where - for our example - BLUE will attack
-- RED planes, despite being on RED territory. Think of a no-fly zone or an limited area of engagement. Conflict zones take precedence over borders, i.e. they can overlap all borders.
--
-- ### Code it
--
-- -- Set up a basic system for the blue side, we'll reside on Kutaisi, and use GROUP objects with "Blue EWR" in the name as EW Radar Systems.
-- local mywing = EASYGCICAP:New("Blue CAP Operations",AIRBASE.Caucasus.Kutaisi,"blue","Blue EWR")
--
-- -- Add a CAP patrol point belonging to our airbase, we'll be at 30k ft doing 400 kn, initial direction 90 degrees (East), leg 20NM
-- mywing:AddPatrolPointCAP(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone 1"):GetCoordinate(),30000,400,90,20)
--
-- -- Add a Squadron with template "Blue Sq1 M2000c", 20 airframes, skill good, Modex starting with 102 and skin "Vendee Jeanne"
-- mywing:AddSquadron("Blue Sq1 M2000c","CAP Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.GOOD,102,"ec1.5_Vendee_Jeanne_clean")
--
-- -- Add a couple of zones
-- -- We'll defend our own border
-- mywing:AddAcceptZone(ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) ))
-- -- We'll attack intruders also here - conflictzones can overlap borders(!) - limited zone of engagement
-- mywing:AddConflictZone(ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" )))
-- -- We'll leave the reds alone on their turf
-- mywing:AddRejectZone(ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) ))
--
-- -- Optional - Draw the borders on the map so we see what's going on
-- -- Set up borders on map
-- local BlueBorder = ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) )
-- BlueBorder:DrawZone(-1,{0,0,1},1,FillColor,FillAlpha,1,true)
-- local ConflictZone = ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" ))
-- ConflictZone:DrawZone(-1,{1,1,0},1,FillColor,FillAlpha,2,true)
-- local BlueNoGoZone = ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) )
-- BlueNoGoZone:DrawZone(-1,{1,0,0},1,FillColor,FillAlpha,4,true)
--
-- ### Add a second airwing with squads and own CAP point (optional)
--
-- -- Set this up at Sukhumi
-- mywing:AddAirwing(AIRBASE.Caucasus.Sukhumi_Babushara,"Blue CAP Sukhumi")
-- -- CAP Point "Blue Zone 2"
-- mywing:AddPatrolPointCAP(AIRBASE.Caucasus.Sukhumi_Babushara,ZONE:FindByName("Blue Zone 2"):GetCoordinate(),30000,400,90,20)
--
-- -- This one has two squadrons to choose from
-- mywing:AddSquadron("Blue Sq3 F16","CAP Sukhumi II",AIRBASE.Caucasus.Sukhumi_Babushara,20,AI.Skill.GOOD,402,"JASDF 6th TFS 43-8526 Skull Riders")
-- mywing:AddSquadron("Blue Sq2 F15","CAP Sukhumi I",AIRBASE.Caucasus.Sukhumi_Babushara,20,AI.Skill.GOOD,202,"390th Fighter SQN")
--
-- ### Add a tanker (optional)
--
-- -- **Note** If you need different tanker types, i.e. Boom and Drogue, set them up at different AirWings!
-- -- Add a tanker point
-- mywing:AddPatrolPointTanker(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone Tanker"):GetCoordinate(),20000,280,270,50)
-- -- Add a tanker squad - Radio 251 AM, TACAN 51Y
-- mywing:AddTankerSquadron("Blue Tanker","Tanker Ops Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.EXCELLENT,602,nil,251,radio.modulation.AM,51)
--
-- ### Add an AWACS (optional)
--
-- -- Add an AWACS point
-- mywing:AddPatrolPointAwacs(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone AWACS"):GetCoordinate(),25000,300,270,50)
-- -- Add an AWACS squad - Radio 251 AM, TACAN 51Y
-- mywing:AddAWACSSquadron("Blue AWACS","AWACS Ops Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.AVERAGE,702,nil,271,radio.modulation.AM)
--
-- # Fine-Tuning
--
-- ## Change Defaults
--
-- * @{#EASYGCICAP.SetDefaultResurrection}: Set how many seconds the AirWing stays inoperable after the AirWing STATIC HQ ist destroyed, default 900 secs.
-- * @{#EASYGCICAP.SetDefaultCAPSpeed}: Set how many knots the CAP flights should do (will be altitude corrected), default 300 kn.
-- * @{#EASYGCICAP.SetDefaultCAPAlt}: Set at which altitude (ASL) the CAP planes will fly, default 25,000 ft.
-- * @{#EASYGCICAP.SetDefaultCAPDirection}: Set the initial direction from the CAP point the planes will fly in degrees, default is 90°.
-- * @{#EASYGCICAP.SetDefaultCAPLeg}: Set the length of the CAP leg, default is 15 NM.
-- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 2.
-- * @{#EASYGCICAP.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100.
-- * @{#EASYGCICAP.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
-- * @{#EASYGCICAP.SetDefaultEngageRange}: Set max engage range for CAP flights if they detect intruders, defaults to 50.
-- * @{#EASYGCICAP.SetMaxAliveMissions}: Set max parallel missions can be done (CAP+GCI+Alert5+Tanker+AWACS), defaults to 8.
-- * @{#EASYGCICAP.SetDefaultRepeatOnFailure}: Set max repeats on failure for intercepting/killing intruders, defaults to 3.
-- * @{#EASYGCICAP.SetTankerAndAWACSInvisible}: Set Tanker and AWACS to be invisible to enemy AI eyes. Is set to `true` by default.
--
-- ## Debug and Monitor
--
-- mywing.debug = true -- log information
-- mywing.Monitor = true -- show some statistics on screen
--
--
-- @field #EASYGCICAP
EASYGCICAP = {
ClassName = "EASYGCICAP",
overhead = 0.75,
capgrouping = 2,
airbasename = nil,
airbase = nil,
coalition = "blue",
alias = nil,
wings = {},
Intel = nil,
resurrection = 900,
capspeed = 300,
capalt = 25000,
capdir = 45,
capleg = 15,
maxinterceptsize = 2,
missionrange = 100,
noalert5 = 4,
ManagedAW = {},
ManagedSQ = {},
ManagedCP = {},
ManagedTK = {},
ManagedEWR = {},
ManagedREC = {},
MaxAliveMissions = 8,
debug = false,
engagerange = 50,
repeatsonfailure = 3,
GoZoneSet = nil,
NoGoZoneSet = nil,
ConflictZoneSet = nil,
Monitor = false,
TankerInvisible = true,
CapFormation = nil,
ReadyFlightGroups = {},
DespawnAfterLanding = false,
DespawnAfterHolding = true,
ListOfAuftrag = {},
defaulttakeofftype = "hot",
FuelLowThreshold = 25,
FuelCriticalThreshold = 10,
showpatrolpointmarks = false,
}
--- Internal Squadron data type
-- @type EASYGCICAP.Squad
-- @field #string TemplateName
-- @field #string SquadName
-- @field #string AirbaseName
-- @field #number AirFrames
-- @field #string Skill
-- @field #string Modex
-- @field #string Livery
-- @field #boolean Tanker
-- @field #boolean AWACS
-- @field #boolean RECON
-- @field #number Frequency
-- @field #number Modulation
-- @field #number TACAN
--- Internal Wing data type
-- @type EASYGCICAP.Wing
-- @field #string AirbaseName
-- @field #string Alias
-- @field #string CapZoneName
--- Internal CapPoint data type
-- @type EASYGCICAP.CapPoint
-- @field #string AirbaseName
-- @field Core.Point#COORDINATE Coordinate
-- @field #number Altitude
-- @field #number Speed
-- @field #number Heading
-- @field #number LegLength
--- EASYGCICAP class version.
-- @field #string version
EASYGCICAP.version="0.1.27"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: TBD
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new GCICAP Manager
-- @param #EASYGCICAP self
-- @param #string Alias A Name for this GCICAP
-- @param #string AirbaseName Name of the Home Airbase
-- @param #string Coalition Coalition, e.g. "blue" or "red"
-- @param #string EWRName (Partial) group name of the EWR system of the coalition, e.g. "Red EWR", can be handed in as table of names, e.g.{"EWR","Radar","SAM"}
-- @return #EASYGCICAP self
function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #EASYGCICAP
-- defaults
self.alias = Alias or AirbaseName.." CAP Wing"
self.coalitionname = string.lower(Coalition) or "blue"
self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
self.wings = {}
if type(EWRName) == "string" then EWRName = {EWRName} end
self.EWRName = EWRName --or self.coalitionname.." EWR"
--self.CapZoneName = CapZoneName
self.airbasename = AirbaseName
self.airbase = AIRBASE:FindByName(self.airbasename)
self.GoZoneSet = SET_ZONE:New()
self.NoGoZoneSet = SET_ZONE:New()
self.ConflictZoneSet = SET_ZONE:New()
self.resurrection = 900
self.capspeed = 300
self.capalt = 25000
self.capdir = 90
self.capleg = 15
self.capgrouping = 2
self.missionrange = 100
self.noalert5 = 2
self.MaxAliveMissions = 8
self.engagerange = 50
self.repeatsonfailure = 3
self.Monitor = false
self.TankerInvisible = true
self.CapFormation = ENUMS.Formation.FixedWing.FingerFour.Group
self.DespawnAfterLanding = false
self.DespawnAfterHolding = true
self.ListOfAuftrag = {}
self.defaulttakeofftype = "hot"
self.FuelLowThreshold = 25
self.FuelCriticalThreshold = 10
self.showpatrolpointmarks = false
-- Set some string id for output to DCS.log file.
self.lid=string.format("EASYGCICAP %s | ", self.alias)
-- Add FSM transitions.
-- From State --> Event --> To State
self:SetStartState("Stopped")
self:AddTransition("Stopped", "Start", "Running")
self:AddTransition("Running", "Stop", "Stopped")
self:AddTransition("*", "Status", "*")
self:AddAirwing(self.airbasename,self.alias,self.CapZoneName)
self:I(self.lid.."Created new instance (v"..self.version..")")
self:__Start(math.random(6,12))
return self
end
-------------------------------------------------------------------------
-- Functions
-------------------------------------------------------------------------
--- Get a specific managed AirWing by name
-- @param #EASYGCICAP self
-- @param #string AirbaseName Airbase name of the home of this wing.
-- @return Ops.AirWing#AIRWING Airwing or nil if not found
function EASYGCICAP:GetAirwing(AirbaseName)
self:T(self.lid.."GetAirwing")
if self.wings[AirbaseName] then
return self.wings[AirbaseName][1]
end
return nil
end
--- Get a table of all managed AirWings
-- @param #EASYGCICAP self
-- @return #table Table of Ops.AirWing#AIRWING Airwings
function EASYGCICAP:GetAirwingTable()
self:T(self.lid.."GetAirwingTable")
local Wingtable = {}
for _,_object in pairs(self.wings or {}) do
table.insert(Wingtable,_object[1])
end
return Wingtable
end
--- Set "fuel low" threshold for CAP and INTERCEPT flights.
-- @param #EASYGCICAP self
-- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 25.
-- @return #EASYGCICAP self
function EASYGCICAP:SetFuelLow(Percent)
self:T(self.lid.."SetFuelLow")
self.FuelLowThreshold = Percent or 25
return self
end
--- Set markers on the map for Patrol Points.
-- @param #EASYGCICAP self
-- @param #boolean onoff Set to true to switch markers on.
-- @return #EASYGCICAP self
function EASYGCICAP:ShowPatrolPointMarkers(onoff)
if onoff then
self.showpatrolpointmarks = true
else
self.showpatrolpointmarks = false
end
return self
end
--- Set "fuel critical" threshold for CAP and INTERCEPT flights.
-- @param #EASYGCICAP self
-- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 10.
-- @return #EASYGCICAP self
function EASYGCICAP:SetFuelCritical(Percent)
self:T(self.lid.."SetFuelCritical")
self.FuelCriticalThreshold = Percent or 10
return self
end
--- Set CAP formation.
-- @param #EASYGCICAP self
-- @param #number Formation Formation to fly, defaults to ENUMS.Formation.FixedWing.FingerFour.Group
-- @return #EASYGCICAP self
function EASYGCICAP:SetCAPFormation(Formation)
self:T(self.lid.."SetCAPFormation")
self.CapFormation = Formation
return self
end
--- Set Tanker and AWACS to be invisible to enemy AI eyes
-- @param #EASYGCICAP self
-- @param #boolean Switch Set to true or false, by default this is set to true already
-- @return #EASYGCICAP self
function EASYGCICAP:SetTankerAndAWACSInvisible(Switch)
self:T(self.lid.."SetTankerAndAWACSInvisible")
self.TankerInvisible = Switch
return self
end
--- (internal) Count alive missions in our internal stack.
-- @param #EASYGCICAP self
-- @return #number count
function EASYGCICAP:_CountAliveAuftrags()
local alive = 0
for _,_auftrag in pairs(self.ListOfAuftrag) do
local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG
if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then
alive = alive + 1
end
end
return alive
end
--- Set Maximum of alive missions created by this instance to stop airplanes spamming the map
-- @param #EASYGCICAP self
-- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Intercept-Missions + Alert5-Missions, default is 8
-- @return #EASYGCICAP self
function EASYGCICAP:SetMaxAliveMissions(Maxiumum)
self:T(self.lid.."SetMaxAliveMissions")
self.MaxAliveMissions = Maxiumum or 8
return self
end
--- Add default time to resurrect Airwing building if destroyed
-- @param #EASYGCICAP self
-- @param #number Seconds Seconds, defaults to 900
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultResurrection(Seconds)
self:T(self.lid.."SetDefaultResurrection")
self.resurrection = Seconds or 900
return self
end
--- Add default repeat attempts if an Intruder intercepts fails.
-- @param #EASYGCICAP self
-- @param #number Retries Retries, defaults to 3
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultRepeatOnFailure(Retries)
self:T(self.lid.."SetDefaultRepeatOnFailure")
self.repeatsonfailure = Retries or 3
return self
end
--- Add default take off type for the airwings.
-- @param #EASYGCICAP self
-- @param #string Takeoff Can be "hot", "cold", or "air" - default is "hot".
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultTakeOffType(Takeoff)
self:T(self.lid.."SetDefaultTakeOffType")
self.defaulttakeofftype = Takeoff or "hot"
return self
end
--- Set default CAP Speed in knots
-- @param #EASYGCICAP self
-- @param #number Speed Speed defaults to 300
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultCAPSpeed(Speed)
self:T(self.lid.."SetDefaultSpeed")
self.capspeed = Speed or 300
return self
end
--- Set default CAP Altitude in feet
-- @param #EASYGCICAP self
-- @param #number Altitude Altitude defaults to 25000
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultCAPAlt(Altitude)
self:T(self.lid.."SetDefaultAltitude")
self.capalt = Altitude or 25000
return self
end
--- Set default CAP lieg initial direction in degrees
-- @param #EASYGCICAP self
-- @param #number Direction Direction defaults to 90 (East)
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultCAPDirection(Direction)
self:T(self.lid.."SetDefaultDirection")
self.capdir = Direction or 90
return self
end
--- Set default leg length in NM
-- @param #EASYGCICAP self
-- @param #number Leg Leg defaults to 15
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultCAPLeg(Leg)
self:T(self.lid.."SetDefaultLeg")
self.capleg = Leg or 15
return self
end
--- Set default grouping, i.e. how many airplanes per CAP point
-- @param #EASYGCICAP self
-- @param #number Grouping Grouping defaults to 2
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultCAPGrouping(Grouping)
self:T(self.lid.."SetDefaultCAPGrouping")
self.capgrouping = Grouping or 2
return self
end
--- Set default range planes can fly from their homebase in NM
-- @param #EASYGCICAP self
-- @param #number Range Range defaults to 100 NM
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultMissionRange(Range)
self:T(self.lid.."SetDefaultMissionRange")
self.missionrange = Range or 100
return self
end
--- Set default number of airframes standing by for intercept tasks (visible on the airfield)
-- @param #EASYGCICAP self
-- @param #number Airframes defaults to 2
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultNumberAlert5Standby(Airframes)
self:T(self.lid.."SetDefaultNumberAlert5Standby")
self.noalert5 = math.abs(Airframes) or 2
return self
end
--- Set default engage range for intruders detected by CAP flights in NM.
-- @param #EASYGCICAP self
-- @param #number Range defaults to 50NM
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultEngageRange(Range)
self:T(self.lid.."SetDefaultEngageRange")
self.engagerange = Range or 50
return self
end
--- Set default overhead for intercept calculations
-- @param #EASYGCICAP self
-- @param #number Overhead The overhead to use.
-- @return #EASYGCICAP self
-- @usage Either a CAP Plane or a newly spawned GCI plane will take care of intruders. Standard overhead is 0.75, i.e. a group of 3 intrudes will
-- be managed by 2 planes from the assigned AirWing. There is an maximum missions limitation per AirWing, so we do not spam the skies.
function EASYGCICAP:SetDefaultOverhead(Overhead)
self:T(self.lid.."SetDefaultOverhead")
self.overhead = Overhead or 0.75
return self
end
--- Set default despawning after landing.
-- @param #EASYGCICAP self
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultDespawnAfterLanding()
self:T(self.lid.."SetDefaultDespawnAfterLanding")
self.DespawnAfterLanding = true
self.DespawnAfterHolding = false
return self
end
--- Set default despawning after holding (despawn in air close to AFB).
-- @param #EASYGCICAP self
-- @return #EASYGCICAP self
function EASYGCICAP:SetDefaultDespawnAfterHolding()
self:T(self.lid.."SetDefaultDespawnAfterLanding")
self.DespawnAfterLanding = false
self.DespawnAfterHolding = true
return self
end
--- Set CAP mission start to vary randomly between Start end End seconds.
-- @param #EASYGCICAP self
-- @param #number Start
-- @param #number End
-- @return #EASYGCICAP self
function EASYGCICAP:SetCapStartTimeVariation(Start, End)
self.capOptionVaryStartTime = Start or 5
self.capOptionVaryEndTime = End or 60
return self
end
--- Add an AirWing to the manager
-- @param #EASYGCICAP self
-- @param #string Airbasename
-- @param #string Alias
-- @return #EASYGCICAP self
function EASYGCICAP:AddAirwing(Airbasename, Alias)
self:T(self.lid.."AddAirwing "..Airbasename)
-- Create Airwing data entry
local AWEntry = {} -- #EASYGCICAP.Wing
AWEntry.AirbaseName = Airbasename
AWEntry.Alias = Alias
--AWEntry.CapZoneName = CapZoneName
self.ManagedAW[Airbasename] = AWEntry
return self
end
--- (Internal) Create actual AirWings from the list
-- @param #EASYGCICAP self
-- @return #EASYGCICAP self
function EASYGCICAP:_CreateAirwings()
self:T(self.lid.."_CreateAirwings")
for airbase,data in pairs(self.ManagedAW) do
local wing = data -- #EASYGCICAP.Wing
local afb = wing.AirbaseName
local alias = wing.Alias
--local cz = wing.CapZoneName
self:_AddAirwing(airbase,alias)
end
return self
end
--- (internal) Create and add another AirWing to the manager
-- @param #EASYGCICAP self
-- @param #string Airbasename
-- @param #string Alias
-- @return #EASYGCICAP self
function EASYGCICAP:_AddAirwing(Airbasename, Alias)
self:T(self.lid.."_AddAirwing "..Airbasename)
local CapFormation = self.CapFormation
local DespawnAfterLanding = self.DespawnAfterLanding
local DespawnAfterHolding = self.DespawnAfterHolding
-- Check STATIC name
local check = STATIC:FindByName(Airbasename,false) or UNIT:FindByName(Airbasename)
if check == nil then
MESSAGE:New(self.lid.."There's no warehouse static on the map (wrong naming?) for airbase "..tostring(Airbasename).."!",30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
-- Create Airwing
local CAP_Wing = AIRWING:New(Airbasename,Alias)
CAP_Wing:SetVerbosityLevel(0)
CAP_Wing:SetReportOff()
CAP_Wing:SetMarker(false)
CAP_Wing:SetAirbase(AIRBASE:FindByName(Airbasename))
CAP_Wing:SetRespawnAfterDestroyed()
CAP_Wing:SetNumberCAP(self.capgrouping)
CAP_Wing:SetCapCloseRaceTrack(true)
if self.showpatrolpointmarks then
CAP_Wing:ShowPatrolPointMarkers(true)
end
if self.capOptionVaryStartTime then
CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime)
end
if CapFormation then
CAP_Wing:SetCAPFormation(CapFormation)
end
if #self.ManagedTK > 0 then
CAP_Wing:SetNumberTankerBoom(1)
CAP_Wing:SetNumberTankerProbe(1)
end
if #self.ManagedEWR > 0 then
CAP_Wing:SetNumberAWACS(1)
end
if #self.ManagedREC > 0 then
CAP_Wing:SetNumberRecon(1)
end
CAP_Wing:SetTakeoffType(self.defaulttakeofftype)
CAP_Wing:SetLowFuelThreshold(0.3)
CAP_Wing.RandomAssetScore = math.random(50,100)
CAP_Wing:Start()
local Intel = self.Intel
local TankerInvisible = self.TankerInvisible
local engagerange = self.engagerange
local GoZoneSet = self.GoZoneSet
local NoGoZoneSet = self.NoGoZoneSet
local FuelLow = self.FuelLowThreshold or 25
local FuelCritical = self.FuelCriticalThreshold or 10
function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission)
local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP
if DespawnAfterLanding then
flightgroup:SetDespawnAfterLanding()
elseif DespawnAfterHolding then
flightgroup:SetDespawnAfterHolding()
end
flightgroup:SetDestinationbase(AIRBASE:FindByName(Airbasename))
flightgroup:GetGroup():CommandEPLRS(true,5)
flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch()
flightgroup:GetGroup():SetOptionLandingOverheadBreak()
if Mission.type ~= AUFTRAG.Type.TANKER and Mission.type ~= AUFTRAG.Type.AWACS and Mission.type ~= AUFTRAG.Type.RECON then
flightgroup:SetDetection(true)
flightgroup:SetEngageDetectedOn(engagerange,{"Air"},GoZoneSet,NoGoZoneSet)
flightgroup:SetOutOfAAMRTB()
flightgroup:SetFuelLowRTB(true)
flightgroup:SetFuelLowThreshold(FuelLow)
flightgroup:SetFuelCriticalRTB(true)
flightgroup:SetFuelCriticalThreshold(FuelCritical)
if CapFormation then
flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation)
end
end
if Mission.type == AUFTRAG.Type.TANKER or Mission.type == AUFTRAG.Type.AWACS or Mission.type == AUFTRAG.Type.RECON then
if TankerInvisible then
flightgroup:GetGroup():SetCommandInvisible(true)
end
if Mission.type == AUFTRAG.Type.RECON then
flightgroup:SetDetection(true)
end
end
flightgroup:GetGroup():OptionROTEvadeFire()
flightgroup:SetFuelLowRTB(true)
Intel:AddAgent(flightgroup)
if DespawnAfterHolding then
function flightgroup:onbeforeHolding(From,Event,To)
self:Despawn(1,true)
end
end
end
if self.noalert5 > 0 then
local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
alert:SetRequiredAssets(self.noalert5)
alert:SetRepeat(99)
CAP_Wing:AddMission(alert)
table.insert(self.ListOfAuftrag,alert)
end
self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename }
return self
end
--- Add a CAP patrol point to a Wing
-- @param #EASYGCICAP self
-- @param #string AirbaseName Name of the Wing's airbase
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Defaults to 25000 feet ASL.
-- @param #number Speed Defaults to 300 knots TAS.
-- @param #number Heading Defaults to 90 degrees (East).
-- @param #number LegLength Defaults to 15 NM.
-- @return #EASYGCICAP self
function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
self:T(self.lid.."AddPatrolPointCAP")--..Coordinate:ToStringLLDDM())
local EntryCAP = {} -- #EASYGCICAP.CapPoint
EntryCAP.AirbaseName = AirbaseName
EntryCAP.Coordinate = Coordinate
EntryCAP.Altitude = Altitude or 25000
EntryCAP.Speed = Speed or 300
EntryCAP.Heading = Heading or 90
EntryCAP.LegLength = LegLength or 15
self.ManagedCP[#self.ManagedCP+1] = EntryCAP
if self.debug then
local mark = MARKER:New(Coordinate,self.lid.."Patrol Point"):ToAll()
end
return self
end
--- Add a RECON patrol point to a Wing
-- @param #EASYGCICAP self
-- @param #string AirbaseName Name of the Wing's airbase
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Defaults to 25000 feet.
-- @param #number Speed Defaults to 300 knots.
-- @param #number Heading Defaults to 90 degrees (East).
-- @param #number LegLength Defaults to 15 NM.
-- @return #EASYGCICAP self
function EASYGCICAP:AddPatrolPointRecon(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
self:T(self.lid.."AddPatrolPointRecon "..Coordinate:ToStringLLDDM())
local EntryCAP = {} -- #EASYGCICAP.CapPoint
EntryCAP.AirbaseName = AirbaseName
EntryCAP.Coordinate = Coordinate
EntryCAP.Altitude = Altitude or 25000
EntryCAP.Speed = Speed or 300
EntryCAP.Heading = Heading or 90
EntryCAP.LegLength = LegLength or 15
self.ManagedREC[#self.ManagedREC+1] = EntryCAP
if self.debug then
local mark = MARKER:New(Coordinate,self.lid.."Patrol Point Recon"):ToAll()
end
return self
end
--- Add a TANKER patrol point to a Wing
-- @param #EASYGCICAP self
-- @param #string AirbaseName Name of the Wing's airbase
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Defaults to 25000 feet.
-- @param #number Speed Defaults to 300 knots.
-- @param #number Heading Defaults to 90 degrees (East).
-- @param #number LegLength Defaults to 15 NM.
-- @return #EASYGCICAP self
function EASYGCICAP:AddPatrolPointTanker(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
self:T(self.lid.."AddPatrolPointTanker "..Coordinate:ToStringLLDDM())
local EntryCAP = {} -- #EASYGCICAP.CapPoint
EntryCAP.AirbaseName = AirbaseName
EntryCAP.Coordinate = Coordinate
EntryCAP.Altitude = Altitude or 25000
EntryCAP.Speed = Speed or 300
EntryCAP.Heading = Heading or 90
EntryCAP.LegLength = LegLength or 15
self.ManagedTK[#self.ManagedTK+1] = EntryCAP
if self.debug then
local mark = MARKER:New(Coordinate,self.lid.."Patrol Point Tanker"):ToAll()
end
return self
end
--- Add an AWACS patrol point to a Wing
-- @param #EASYGCICAP self
-- @param #string AirbaseName Name of the Wing's airbase
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Defaults to 25000 feet.
-- @param #number Speed Defaults to 300 knots.
-- @param #number Heading Defaults to 90 degrees (East).
-- @param #number LegLength Defaults to 15 NM.
-- @return #EASYGCICAP self
function EASYGCICAP:AddPatrolPointAwacs(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
self:T(self.lid.."AddPatrolPointAwacs "..Coordinate:ToStringLLDDM())
local EntryCAP = {} -- #EASYGCICAP.CapPoint
EntryCAP.AirbaseName = AirbaseName
EntryCAP.Coordinate = Coordinate
EntryCAP.Altitude = Altitude or 25000
EntryCAP.Speed = Speed or 300
EntryCAP.Heading = Heading or 90
EntryCAP.LegLength = LegLength or 15
self.ManagedEWR[#self.ManagedEWR+1] = EntryCAP
if self.debug then
local mark = MARKER:New(Coordinate,self.lid.."Patrol Point AWACS"):ToAll()
end
return self
end
--- (Internal) Set actual Tanker Points from the list
-- @param #EASYGCICAP self
-- @return #EASYGCICAP self
function EASYGCICAP:_SetTankerPatrolPoints()
self:T(self.lid.."_SetTankerPatrolPoints")
for _,_data in pairs(self.ManagedTK) do
local data = _data --#EASYGCICAP.CapPoint
self:T("Airbasename = "..data.AirbaseName)
if not self.wings[data.AirbaseName] then
MESSAGE:New(self.lid.."You are trying to create a TANKER point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
local Heading = data.Heading
local LegLength = data.LegLength
Wing:AddPatrolPointTANKER(Coordinate,Altitude,Speed,Heading,LegLength)
end
return self
end
--- (Internal) Set actual Awacs Points from the list
-- @param #EASYGCICAP self
-- @return #EASYGCICAP self
function EASYGCICAP:_SetAwacsPatrolPoints()
self:T(self.lid.."_SetAwacsPatrolPoints")
for _,_data in pairs(self.ManagedEWR) do
local data = _data --#EASYGCICAP.CapPoint
self:T("Airbasename = "..data.AirbaseName)
if not self.wings[data.AirbaseName] then
MESSAGE:New(self.lid.."You are trying to create an AWACS point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
local Heading = data.Heading
local LegLength = data.LegLength
Wing:AddPatrolPointAWACS(Coordinate,Altitude,Speed,Heading,LegLength)
end
return self
end
--- (Internal) Set actual PatrolPoints from the list
-- @param #EASYGCICAP self
-- @return #EASYGCICAP self
function EASYGCICAP:_SetCAPPatrolPoints()
self:T(self.lid.."_SetCAPPatrolPoints")
for _,_data in pairs(self.ManagedCP) do
local data = _data --#EASYGCICAP.CapPoint
self:T("Airbasename = "..data.AirbaseName)
if not self.wings[data.AirbaseName] then
MESSAGE:New(self.lid.."You are trying to create a CAP point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
local Heading = data.Heading
local LegLength = data.LegLength
Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength)
end
return self
end
--- (Internal) Set actual PatrolPoints from the list
-- @param #EASYGCICAP self
-- @return #EASYGCICAP self
function EASYGCICAP:_SetReconPatrolPoints()
self:T(self.lid.."_SetReconPatrolPoints")
for _,_data in pairs(self.ManagedREC) do
local data = _data --#EASYGCICAP.CapPoint
self:T("Airbasename = "..data.AirbaseName)
if not self.wings[data.AirbaseName] then
MESSAGE:New(self.lid.."You are trying to create a RECON point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
return
end
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
local Heading = data.Heading
local LegLength = data.LegLength
Wing:AddPatrolPointRecon(Coordinate,Altitude,Speed,Heading,LegLength)
end
return self
end
--- (Internal) Create actual Squadrons from the list
-- @param #EASYGCICAP self
-- @return #EASYGCICAP self
function EASYGCICAP:_CreateSquads()
self:T(self.lid.."_CreateSquads")
for name,data in pairs(self.ManagedSQ) do
local squad = data -- #EASYGCICAP.Squad
local SquadName = name
local TemplateName = squad.TemplateName
local AirbaseName = squad.AirbaseName
local AirFrames = squad.AirFrames
local Skill = squad.Skill
local Modex = squad.Modex
local Livery = squad.Livery
local Frequeny = squad.Frequency
local Modulation = squad.Modulation
local TACAN = squad.TACAN
if squad.Tanker then
self:_AddTankerSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequeny,Modulation,TACAN)
elseif squad.AWACS then
self:_AddAWACSSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequeny,Modulation)
elseif squad.RECON then
self:_AddReconSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery)
else
self:_AddSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery)
end
end
return self
end
--- Add a Squadron to an Airwing of the manager
-- @param #EASYGCICAP self
-- @param #string TemplateName Name of the group template.
-- @param #string SquadName Squadron name - must be unique!
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
-- @param #number AirFrames Number of available airframes, e.g. 20.
-- @param #string Skill (optional) Skill level, e.g. AI.Skill.AVERAGE
-- @param #string Modex (optional) Modex to be used,e.g. 402.
-- @param #string Livery (optional) Livery name to be used.
-- @return #EASYGCICAP self
function EASYGCICAP:AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery)
self:T(self.lid.."AddSquadron "..SquadName)
-- Add Squadron Data
local EntrySQ = {} -- #EASYGCICAP.Squad
EntrySQ.TemplateName = TemplateName
EntrySQ.SquadName = SquadName
EntrySQ.AirbaseName = AirbaseName
EntrySQ.AirFrames = AirFrames or 20
EntrySQ.Skill = Skill or AI.Skill.AVERAGE
EntrySQ.Modex = Modex or 402
EntrySQ.Livery = Livery
self.ManagedSQ[SquadName] = EntrySQ
return self
end
--- Add a Recon Squadron to an Airwing of the manager
-- @param #EASYGCICAP self
-- @param #string TemplateName Name of the group template.
-- @param #string SquadName Squadron name - must be unique!
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
-- @param #number AirFrames Number of available airframes, e.g. 20.
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
-- @param #string Modex (optional) Modex to be used,e.g. 402.
-- @param #string Livery (optional) Livery name to be used.
-- @return #EASYGCICAP self
function EASYGCICAP:AddReconSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery)
self:T(self.lid.."AddReconSquadron "..SquadName)
-- Add Squadron Data
local EntrySQ = {} -- #EASYGCICAP.Squad
EntrySQ.TemplateName = TemplateName
EntrySQ.SquadName = SquadName
EntrySQ.AirbaseName = AirbaseName
EntrySQ.AirFrames = AirFrames or 20
EntrySQ.Skill = Skill or AI.Skill.AVERAGE
EntrySQ.Modex = Modex or 402
EntrySQ.Livery = Livery
EntrySQ.RECON = true
self.ManagedSQ[SquadName] = EntrySQ
return self
end
--- Add a Tanker Squadron to an Airwing of the manager
-- @param #EASYGCICAP self
-- @param #string TemplateName Name of the group template.
-- @param #string SquadName Squadron name - must be unique!
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
-- @param #number AirFrames Number of available airframes, e.g. 20.
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
-- @param #string Modex (optional) Modex to be used,e.g. 402.
-- @param #string Livery (optional) Livery name to be used.
-- @param #number Frequency (optional) Radio Frequency to be used.
-- @param #number Modulation (optional) Radio Modulation to be used, e.g. radio.modulation.AM or radio.modulation.FM
-- @param #number TACAN (optional) TACAN channel, e.g. 71, resulting in Channel 71Y
-- @return #EASYGCICAP self
function EASYGCICAP:AddTankerSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery, Frequency, Modulation, TACAN)
self:T(self.lid.."AddTankerSquadron "..SquadName)
-- Add Squadron Data
local EntrySQ = {} -- #EASYGCICAP.Squad
EntrySQ.TemplateName = TemplateName
EntrySQ.SquadName = SquadName
EntrySQ.AirbaseName = AirbaseName
EntrySQ.AirFrames = AirFrames or 20
EntrySQ.Skill = Skill or AI.Skill.AVERAGE
EntrySQ.Modex = Modex or 602
EntrySQ.Livery = Livery
EntrySQ.Frequency = Frequency
EntrySQ.Modulation = Livery
EntrySQ.TACAN = TACAN
EntrySQ.Tanker = true
self.ManagedSQ[SquadName] = EntrySQ
return self
end
--- Add an AWACS Squadron to an Airwing of the manager
-- @param #EASYGCICAP self
-- @param #string TemplateName Name of the group template.
-- @param #string SquadName Squadron name - must be unique!
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
-- @param #number AirFrames Number of available airframes, e.g. 20.
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
-- @param #string Modex (optional) Modex to be used,e.g. 402.
-- @param #string Livery (optional) Livery name to be used.
-- @param #number Frequency (optional) Radio Frequency to be used.
-- @param #number Modulation (optional) Radio Modulation to be used, e.g. radio.modulation.AM or radio.modulation.FM
-- @return #EASYGCICAP self
function EASYGCICAP:AddAWACSSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery, Frequency, Modulation)
self:T(self.lid.."AddAWACSSquadron "..SquadName)
-- Add Squadron Data
local EntrySQ = {} -- #EASYGCICAP.Squad
EntrySQ.TemplateName = TemplateName
EntrySQ.SquadName = SquadName
EntrySQ.AirbaseName = AirbaseName
EntrySQ.AirFrames = AirFrames or 20
EntrySQ.Skill = Skill or AI.Skill.AVERAGE
EntrySQ.Modex = Modex or 702
EntrySQ.Livery = Livery
EntrySQ.Frequency = Frequency
EntrySQ.Modulation = Livery
EntrySQ.AWACS = true
self.ManagedSQ[SquadName] = EntrySQ
return self
end
--- (Internal) Add a Squadron to an Airwing of the manager
-- @param #EASYGCICAP self
-- @param #string TemplateName Name of the group template.
-- @param #string SquadName Squadron name - must be unique!
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
-- @param #number AirFrames Number of available airframes, e.g. 20.
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
-- @param #string Modex (optional) Modex to be used,e.g. 402.
-- @param #string Livery (optional) Livery name to be used.
-- @param #number Frequency (optional) Radio Frequency to be used.
-- @param #number Modulation (optional) Radio Modulation to be used, e.g. radio.modulation.AM or radio.modulation.FM
-- @return #EASYGCICAP self
function EASYGCICAP:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery, Frequency, Modulation)
self:T(self.lid.."_AddSquadron "..SquadName)
-- Add Squadrons
local Squadron_One = SQUADRON:New(TemplateName,AirFrames,SquadName)
Squadron_One:AddMissionCapability({AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5})
--Squadron_One:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5},75)
return self
end
--- (Internal) Add a Recon Squadron to an Airwing of the manager
-- @param #EASYGCICAP self
-- @param #string TemplateName Name of the group template.
-- @param #string SquadName Squadron name - must be unique!
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
-- @param #number AirFrames Number of available airframes, e.g. 20.
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
-- @param #string Modex (optional) Modex to be used,e.g. 402.
-- @param #string Livery (optional) Livery name to be used.
-- @return #EASYGCICAP self
function EASYGCICAP:_AddReconSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery)
self:T(self.lid.."_AddReconSquadron "..SquadName)
-- Add Squadrons
local Squadron_One = SQUADRON:New(TemplateName,AirFrames,SquadName)
Squadron_One:AddMissionCapability({AUFTRAG.Type.RECON})
--Squadron_One:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.RECON},75)
return self
end
--- (Internal) Add a Tanker Squadron to an Airwing of the manager
-- @param #EASYGCICAP self
-- @param #string TemplateName Name of the group template.
-- @param #string SquadName Squadron name - must be unique!
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
-- @param #number AirFrames Number of available airframes, e.g. 20.
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
-- @param #string Modex (optional) Modex to be used,e.g. 402.
-- @param #string Livery (optional) Livery name to be used.
-- @param #number Frequency (optional) Radio frequency of the Tanker
-- @param #number Modulation (Optional) Radio modulation of the Tanker
-- @param #number TACAN (Optional) TACAN Channel to be used, will always be an "Y" channel
-- @return #EASYGCICAP self
function EASYGCICAP:_AddTankerSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery, Frequency, Modulation, TACAN)
self:T(self.lid.."_AddTankerSquadron "..SquadName)
-- Add Squadrons
local Squadron_One = SQUADRON:New(TemplateName,AirFrames,SquadName)
Squadron_One:AddMissionCapability({AUFTRAG.Type.TANKER})
--Squadron_One:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
Squadron_One:SetRadio(Frequency,Modulation)
Squadron_One:AddTacanChannel(TACAN,TACAN)
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.TANKER},75)
return self
end
--- (Internal) Add a AWACS Squadron to an Airwing of the manager
-- @param #EASYGCICAP self
-- @param #string TemplateName Name of the group template.
-- @param #string SquadName Squadron name - must be unique!
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
-- @param #number AirFrames Number of available airframes, e.g. 20.
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
-- @param #string Modex (optional) Modex to be used,e.g. 402.
-- @param #string Livery (optional) Livery name to be used.
-- @param #number Frequency (optional) Radio frequency of the AWACS
-- @param #number Modulation (Optional) Radio modulation of the AWACS
-- @return #EASYGCICAP self
function EASYGCICAP:_AddAWACSSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery, Frequency, Modulation)
self:T(self.lid.."_AddAWACSSquadron "..SquadName)
-- Add Squadrons
local Squadron_One = SQUADRON:New(TemplateName,AirFrames,SquadName)
Squadron_One:AddMissionCapability({AUFTRAG.Type.AWACS})
--Squadron_One:SetFuelLowRefuel(true)
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
Squadron_One:SetRadio(Frequency,Modulation)
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.AWACS},75)
return self
end
--- Add a zone to the accepted zones set.
-- @param #EASYGCICAP self
-- @param Core.Zone#ZONE_BASE Zone
-- @return #EASYGCICAP self
function EASYGCICAP:AddAcceptZone(Zone)
self:T(self.lid.."AddAcceptZone")
self.GoZoneSet:AddZone(Zone)
return self
end
--- Add a zone to the rejected zones set.
-- @param #EASYGCICAP self
-- @param Core.Zone#ZONE_BASE Zone
-- @return #EASYGCICAP self
function EASYGCICAP:AddRejectZone(Zone)
self:T(self.lid.."AddRejectZone")
self.NoGoZoneSet:AddZone(Zone)
return self
end
--- Add a zone to the conflict zones set.
-- @param #EASYGCICAP self
-- @param Core.Zone#ZONE_BASE Zone
-- @return #EASYGCICAP self
function EASYGCICAP:AddConflictZone(Zone)
self:T(self.lid.."AddConflictZone")
self.ConflictZoneSet:AddZone(Zone)
self.GoZoneSet:AddZone(Zone)
return self
end
--- (Internal) Try to assign the intercept to a FlightGroup already in air and ready.
-- @param #EASYGCICAP self
-- @param #table ReadyFlightGroups ReadyFlightGroups
-- @param Ops.Auftrag#AUFTRAG InterceptAuftrag The Auftrag
-- @param Wrapper.Group#GROUP Group The Target
-- @param #number WingSize Calculated number of Flights
-- @return #boolean assigned
-- @return #number leftover
function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize)
self:I("_TryAssignIntercept for size "..WingSize or 1)
local assigned = false
local wingsize = WingSize or 1
local mindist = 0
local disttable = {}
if Group and Group:IsAlive() then
local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0)
self:I(self.lid..string.format("Assignment for %s",Group:GetName()))
for _name,_FG in pairs(ReadyFlightGroups or {}) do
local FG = _FG -- Ops.FlightGroup#FLIGHTGROUP
local fcoord = FG:GetCoordinate()
local dist = math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1))
self:I(self.lid..string.format("FG %s Distance %dkm",_name,dist))
disttable[#disttable+1] = { FG=FG, dist=dist}
if dist>mindist then mindist=dist end
end
local function sortDistance(a, b)
return a.dist < b.dist
end
table.sort(disttable, sortDistance)
for _,_entry in ipairs(disttable) do
local FG = _entry.FG -- Ops.FlightGroup#FLIGHTGROUP
FG:AddMission(InterceptAuftrag)
local cm = FG:GetMissionCurrent()
if cm then cm:Cancel() end
wingsize = wingsize - 1
self:I(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist))
if wingsize == 0 then
assigned = true
break
end
end
end
return assigned, wingsize
end
--- Here, we'll decide if we need to launch an intercepting flight, and from where
-- @param #EASYGCICAP self
-- @param Ops.Intel#INTEL.Cluster Cluster
-- @return #EASYGCICAP self
function EASYGCICAP:_AssignIntercept(Cluster)
-- Here, we'll decide if we need to launch an intercepting flight, and from where
local overhead = self.overhead
local capspeed = self.capspeed + 100
local capalt = self.capalt
local maxsize = self.maxinterceptsize
local repeatsonfailure = self.repeatsonfailure
local wings = self.wings
local ctlpts = self.ManagedCP
local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping
local nogozoneset = self.NoGoZoneSet
local conflictzoneset = self.ConflictZoneSet
local ReadyFlightGroups = self.ReadyFlightGroups
-- Aircraft?
if Cluster.ctype ~= INTEL.Ctype.AIRCRAFT then return end
-- Threatlevel 0..10
local contact = self.Intel:GetHighestThreatContact(Cluster)
local name = contact.groupname --#string
local threat = contact.threatlevel --#number
local position = self.Intel:CalcClusterFuturePosition(Cluster,300)
-- calculate closest zone
local bestdistance = 2000*1000 -- 2000km
local targetairwing = nil -- Ops.Airwing#AIRWING
local targetawname = "" -- #string
local clustersize = self.Intel:ClusterCountUnits(Cluster) or 1
local wingsize = math.abs(overhead * (clustersize+1))
if wingsize > maxsize then wingsize = maxsize end
-- existing mission, and if so - done?
local retrymission = true
if Cluster.mission and (not Cluster.mission:IsOver()) then
retrymission = false
end
if (retrymission) and (wingsize >= 1) then
MESSAGE:New(string.format("**** %s Interceptors need wingsize %d", UTILS.GetCoalitionName(self.coalition), wingsize),15,"CAPGCI"):ToAllIf(self.debug):ToLog()
for _,_data in pairs (wings) do
local airwing = _data[1] -- Ops.Airwing#AIRWING
local zone = _data[2] -- Core.Zone#ZONE
local zonecoord = zone:GetCoordinate()
local name = _data[3] -- #string
local coa = AIRBASE:FindByName(name):GetCoalition()
local distance = position:DistanceFromPointVec2(zonecoord)
local airframes = airwing:CountAssets(true)
local samecoalitionab = coa == self.coalition and true or false
if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then
bestdistance = distance
targetairwing = airwing
targetawname = name
end
end
for _,_data in pairs (ctlpts) do
--local airwing = _data[1] -- Ops.Airwing#AIRWING
--local zone = _data[2] -- Core.Zone#ZONE
--local zonecoord = zone:GetCoordinate()
--local name = _data[3] -- #string
local data = _data -- #EASYGCICAP.CapPoint
local name = data.AirbaseName
local zonecoord = data.Coordinate
local airwing = wings[name][1]
local coa = AIRBASE:FindByName(name):GetCoalition()
local samecoalitionab = coa == self.coalition and true or false
local distance = position:DistanceFromPointVec2(zonecoord)
local airframes = airwing:CountAssets(true)
if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then
bestdistance = distance
targetairwing = airwing -- Ops.Airwing#AIRWING
targetawname = name
end
end
local text = string.format("Closest Airwing is %s", targetawname)
local m = MESSAGE:New(text,10,"CAPGCI"):ToAllIf(self.debug):ToLog()
-- Do we have a matching airwing?
if targetairwing then
local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort)
local missioncount = self:_CountAliveAuftrags()
-- Enough airframes on mission already?
self:T(self.lid.." Assets on Mission "..AssetCount)
if missioncount < MaxAliveMissions then
local repeats = repeatsonfailure
local InterceptAuftrag = AUFTRAG:NewINTERCEPT(contact.group)
:SetMissionRange(150)
:SetPriority(1,true,1)
--:SetRequiredAssets(wingsize)
:SetRepeatOnFailure(repeats)
:SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt))
:SetMissionAltitude(capalt)
if nogozoneset:Count() > 0 then
InterceptAuftrag:AddConditionSuccess(
function(group,zoneset,conflictset)
local success = false
if group and group:IsAlive() then
local coord = group:GetCoordinate()
if coord and zoneset:Count() > 0 and zoneset:IsCoordinateInZone(coord) then
success = true
end
if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then
success = false
end
end
return success
end,
contact.group,
nogozoneset,
conflictzoneset
)
end
table.insert(self.ListOfAuftrag,InterceptAuftrag)
local assigned, rest = self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
if not assigned then
InterceptAuftrag:SetRequiredAssets(rest)
targetairwing:AddMission(InterceptAuftrag)
end
Cluster.mission = InterceptAuftrag
end
else
MESSAGE:New("**** Not enough airframes available or max mission limit reached!",15,"CAPGCI"):ToAllIf(self.debug):ToLog()
end
end
end
--- (Internal) Start detection.
-- @param #EASYGCICAP self
-- @return #EASYGCICAP self
function EASYGCICAP:_StartIntel()
self:T(self.lid.."_StartIntel")
-- Border GCI Detection
local BlueAir_DetectionSetGroup = SET_GROUP:New()
BlueAir_DetectionSetGroup:FilterPrefixes( self.EWRName )
BlueAir_DetectionSetGroup:FilterStart()
-- Intel type detection
local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.alias)
BlueIntel:SetClusterAnalysis(true,false,false)
BlueIntel:SetForgetTime(300)
BlueIntel:SetAcceptZones(self.GoZoneSet)
BlueIntel:SetRejectZones(self.NoGoZoneSet)
BlueIntel:SetConflictZones(self.ConflictZoneSet)
BlueIntel:SetVerbosity(0)
BlueIntel:Start()
if self.debug then
BlueIntel.debug = true
end
local function AssignCluster(Cluster)
self:_AssignIntercept(Cluster)
end
function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster)
AssignCluster(Cluster)
end
self.Intel = BlueIntel
return self
end
-------------------------------------------------------------------------
-- TODO FSM Functions
-------------------------------------------------------------------------
--- (Internal) FSM Function onafterStart
-- @param #EASYGCICAP self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #EASYGCICAP self
function EASYGCICAP:onafterStart(From,Event,To)
self:T({From,Event,To})
self:_StartIntel()
self:_CreateAirwings()
self:_CreateSquads()
self:_SetCAPPatrolPoints()
self:_SetTankerPatrolPoints()
self:_SetAwacsPatrolPoints()
self:_SetReconPatrolPoints()
self:__Status(-10)
return self
end
--- (Internal) FSM Function onbeforeStatus
-- @param #EASYGCICAP self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #EASYGCICAP self
function EASYGCICAP:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
if self:GetState() == "Stopped" then return false end
return self
end
--- (Internal) FSM Function onafterStatus
-- @param #EASYGCICAP self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #EASYGCICAP self
function EASYGCICAP:onafterStatus(From,Event,To)
self:T({From,Event,To})
-- cleanup
local cleaned = false
local cleanlist = {}
for _,_auftrag in pairs(self.ListOfAuftrag) do
local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG
if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then
table.insert(cleanlist,auftrag)
cleaned = true
end
end
if cleaned == true then
self.ListOfAuftrag = nil
self.ListOfAuftrag = cleanlist
end
-- Gather Some Stats
local function counttable(tbl)
local count = 0
for _,_data in pairs(tbl) do
count = count + 1
end
return count
end
local wings = counttable(self.ManagedAW)
local squads = counttable(self.ManagedSQ)
local caps = counttable(self.ManagedCP)
local assets = 0
local instock = 0
local capmission = 0
local interceptmission = 0
local reconmission = 0
local awacsmission = 0
local tankermission = 0
for _,_wing in pairs(self.wings) do
local count = _wing[1]:CountAssetsOnMission(MissionTypes,Cohort)
local count2 = _wing[1]:CountAssets(true,MissionTypes,Attributes)
capmission = capmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})
interceptmission = interceptmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.INTERCEPT})
reconmission = reconmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON})
awacsmission = awacsmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS})
tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
assets = assets + count
instock = instock + count2
local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})
-- update ready groups
self.ReadyFlightGroups = nil
self.ReadyFlightGroups = {}
for _,_asset in pairs(assetsonmission or {}) do
local asset = _asset -- Functional.Warehouse#WAREHOUSE.Assetitem
local FG = asset.flightgroup -- Ops.FlightGroup#FLIGHTGROUP
if FG then
local name = FG:GetName()
local engage = FG:IsEngaging()
local hasmissiles = FG:IsOutOfMissiles() == nil and true or false
local ready = hasmissiles and FG:IsFuelGood() and FG:IsAirborne()
--self:I(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready)))
if ready then
self.ReadyFlightGroups[name] = FG
end
end
end
end
if self.Monitor then
local threatcount = #self.Intel.Clusters or 0
local text = "GCICAP "..self.alias
text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock
text = text.."\nThreats: "..threatcount
text = text.."\nAirWing managed Missions: "..capmission+awacsmission+tankermission+reconmission
text = text.."\n - CAP: "..capmission
text = text.."\n - AWACS: "..awacsmission
text = text.."\n - TANKER: "..tankermission
text = text.."\n - Recon: "..reconmission
text = text.."\nSelf managed Missions:"
text = text.."\n - Mission Limit: "..self.MaxAliveMissions
text = text.."\n - Alert5+Intercept "..self:_CountAliveAuftrags()
MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug)
end
self:__Status(30)
return self
end
--- (Internal) FSM Function onafterStop
-- @param #EASYGCICAP self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #EASYGCICAP self
function EASYGCICAP:onafterStop(From,Event,To)
self:T({From,Event,To})
self.Intel:Stop()
for _,_wing in pairs(self.wings or {}) do
_wing:Stop()
end
return self
end